tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit ea9361e4746e2a88549a16d48b61b08852343c6d
parent 093843b4ac3e5612ea267237f31db7b63abf55f7
Author: Lando <lando@lando.test>
Date:   Wed,  8 Oct 2025 13:01:19 +0000

Merge mozilla-central to autoland

Diffstat:
Mdom/script/ModuleLoader.cpp | 12++++--------
Mdom/script/ScriptLoadHandler.cpp | 6+++---
Mdom/script/ScriptLoader.cpp | 369++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mdom/script/ScriptLoader.h | 35++++++++++++++++++++++++++++-------
Mdom/xul/nsXULElement.cpp | 22++++++++++++----------
Mjs/loader/LoadedScript.cpp | 13++++++-------
Mjs/loader/LoadedScript.h | 89+++++++++++++++++++++----------------------------------------------------------
Mjs/loader/ModuleLoadRequest.cpp | 8--------
Mjs/loader/ModuleLoadRequest.h | 9+++++++--
Mjs/loader/ModuleLoaderBase.cpp | 12+++++++-----
Mjs/loader/ScriptLoadRequest.cpp | 30+++++++++++++++++++++++++++---
Mjs/loader/ScriptLoadRequest.h | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mjs/public/experimental/JSStencil.h | 3---
Mjs/src/frontend/StencilXdr.cpp | 14++------------
Mjs/src/frontend/StencilXdr.h | 4----
Mjs/xpconnect/loader/ScriptCacheActors.cpp | 11+++--------
Mjs/xpconnect/loader/ScriptPreloader.cpp | 18++++++------------
Mjs/xpconnect/loader/ScriptPreloader.h | 9++++-----
18 files changed, 440 insertions(+), 280 deletions(-)

diff --git a/dom/script/ModuleLoader.cpp b/dom/script/ModuleLoader.cpp @@ -95,7 +95,7 @@ bool ModuleLoader::CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) { } nsresult ModuleLoader::StartFetch(ModuleLoadRequest* aRequest) { - if (aRequest->IsCachedStencil()) { + if (aRequest->IsStencil()) { GetScriptLoader()->EmulateNetworkEvents(aRequest); SetModuleFetchStarted(aRequest); return aRequest->OnFetchComplete(NS_OK); @@ -235,7 +235,7 @@ nsresult ModuleLoader::CompileJavaScriptModule( JS::MutableHandle<JSObject*> aModuleOut) { GetScriptLoader()->CalculateCacheFlag(aRequest); - if (aRequest->IsCachedStencil()) { + if (aRequest->IsStencil()) { JS::InstantiateOptions instantiateOptions(aOptions); RefPtr<JS::Stencil> stencil = aRequest->GetStencil(); aModuleOut.set( @@ -262,8 +262,6 @@ nsresult ModuleLoader::CompileJavaScriptModule( return NS_ERROR_FAILURE; } - aRequest->SetStencil(stencil); - JS::InstantiateOptions instantiateOptions(aOptions); aModuleOut.set(JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil, &storage)); @@ -280,7 +278,7 @@ nsresult ModuleLoader::CompileJavaScriptModule( MOZ_ASSERT(!alreadyStarted); } - GetScriptLoader()->TryCacheRequest(aRequest); + GetScriptLoader()->TryCacheRequest(aRequest, stencil); return NS_OK; } @@ -313,8 +311,6 @@ nsresult ModuleLoader::CompileJavaScriptModule( return NS_ERROR_FAILURE; } - aRequest->SetStencil(stencil); - JS::InstantiateOptions instantiateOptions(aOptions); aModuleOut.set( JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil)); @@ -331,7 +327,7 @@ nsresult ModuleLoader::CompileJavaScriptModule( MOZ_ASSERT(!alreadyStarted); } - GetScriptLoader()->TryCacheRequest(aRequest); + GetScriptLoader()->TryCacheRequest(aRequest, stencil); return NS_OK; } diff --git a/dom/script/ScriptLoadHandler.cpp b/dom/script/ScriptLoadHandler.cpp @@ -469,9 +469,9 @@ ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader, // later save the bytecode on the cache entry. if (NS_SUCCEEDED(rv) && mRequest->IsSource() && StaticPrefs::dom_script_loader_bytecode_cache_enabled()) { - mRequest->getLoadedScript()->mCacheInfo = do_QueryInterface(channelRequest); + mRequest->mCacheInfo = do_QueryInterface(channelRequest); LOG(("ScriptLoadRequest (%p): nsICacheInfoChannel = %p", mRequest.get(), - mRequest->getLoadedScript()->mCacheInfo.get())); + mRequest->mCacheInfo.get())); } // we have to mediate and use mRequest. @@ -480,7 +480,7 @@ ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader, // In case of failure, clear the mCacheInfoChannel to avoid keeping it alive. if (NS_FAILED(rv)) { - mRequest->getLoadedScript()->DropDiskCacheReference(); + mRequest->DropDiskCacheReference(); } return rv; diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp @@ -23,7 +23,7 @@ #include "js/Transcoding.h" // JS::TranscodeRange, JS::TranscodeResult, JS::IsTranscodeFailureResult #include "js/Utility.h" #include "js/experimental/CompileScript.h" // JS::FrontendContext, JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::ThreadStackQuotaForSize, JS::CompilationStorage, JS::CompileGlobalScriptToStencil, JS::CompileModuleScriptToStencil, JS::DecodeStencil, JS::PrepareForInstantiate -#include "js/experimental/JSStencil.h" // JS::Stencil, JS::InstantiationStorage, JS::StartCollectingDelazifications, JS::IsStencilCacheable +#include "js/experimental/JSStencil.h" // JS::Stencil, JS::InstantiationStorage, JS::StartCollectingDelazifications, JS::FinishCollectingDelazifications, JS::AbortCollectingDelazifications, JS::IsStencilCacheable #include "js/loader/LoadedScript.h" #include "js/loader/ModuleLoadRequest.h" #include "js/loader/ModuleLoaderBase.h" @@ -636,7 +636,7 @@ static nsSecurityFlags CORSModeToSecurityFlags(CORSMode aCORSMode) { nsresult ScriptLoader::StartClassicLoad( ScriptLoadRequest* aRequest, const Maybe<nsAutoString>& aCharsetForPreload) { - if (aRequest->IsCachedStencil()) { + if (aRequest->IsStencil()) { EmulateNetworkEvents(aRequest); return NS_OK; } @@ -725,7 +725,7 @@ void ScriptLoader::PrepareCacheInfoChannel(nsIChannel* aChannel, ScriptLoadRequest* aRequest) { // To avoid decoding issues, the build-id is part of the bytecode MIME type // constant. - aRequest->getLoadedScript()->DropDiskCacheReference(); + aRequest->DropDiskCacheReference(); nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(aChannel)); if (cic && StaticPrefs::dom_script_loader_bytecode_cache_enabled()) { MOZ_ASSERT(!IsWebExtensionRequest(aRequest), @@ -1153,21 +1153,11 @@ void ScriptLoader::TryUseCache(ScriptLoadRequest* aRequest, ScriptLoadRequestType aRequestType) { if (aRequestType == ScriptLoadRequestType::Inline) { aRequest->NoCacheEntryFound(); - LOG( - ("ScriptLoader (%p): Created LoadedScript (%p) for " - "ScriptLoadRequest(%p) %s.", - this, aRequest->getLoadedScript(), aRequest, - aRequest->mURI->GetSpecOrDefault().get())); return; } if (!mCache) { aRequest->NoCacheEntryFound(); - LOG( - ("ScriptLoader (%p): Created LoadedScript (%p) for " - "ScriptLoadRequest(%p) %s.", - this, aRequest->getLoadedScript(), aRequest, - aRequest->mURI->GetSpecOrDefault().get())); return; } @@ -1175,11 +1165,6 @@ void ScriptLoader::TryUseCache(ScriptLoadRequest* aRequest, auto cacheResult = mCache->Lookup(*this, key, /* aSyncLoad = */ true); if (cacheResult.mState != CachedSubResourceState::Complete) { aRequest->NoCacheEntryFound(); - LOG( - ("ScriptLoader (%p): Created LoadedScript (%p) for " - "ScriptLoadRequest(%p) %s.", - this, aRequest->getLoadedScript(), aRequest, - aRequest->mURI->GetSpecOrDefault().get())); return; } @@ -1188,11 +1173,6 @@ void ScriptLoader::TryUseCache(ScriptLoadRequest* aRequest, // LookupPreloadRequest call. if (NS_FAILED(CheckContentPolicy(aElement, aNonce, aRequest))) { aRequest->NoCacheEntryFound(); - LOG( - ("ScriptLoader (%p): Created LoadedScript (%p) for " - "ScriptLoadRequest(%p) %s.", - this, aRequest->getLoadedScript(), aRequest, - aRequest->mURI->GetSpecOrDefault().get())); return; } } @@ -1200,10 +1180,7 @@ void ScriptLoader::TryUseCache(ScriptLoadRequest* aRequest, aRequest->mNetworkMetadata = cacheResult.mNetworkMetadata; aRequest->CacheEntryFound(cacheResult.mCompleteValue); - LOG( - ("ScriptLoader (%p): Found in-memory cache LoadedScript (%p) for " - "ScriptLoadRequest(%p) %s.", - this, aRequest->getLoadedScript(), aRequest, + LOG(("ScriptLoader (%p): Found in-memory cache for %s.", this, aRequest->mURI->GetSpecOrDefault().get())); if (cacheResult.mCompleteValue->mFetchCount < UINT8_MAX) { @@ -1212,8 +1189,23 @@ void ScriptLoader::TryUseCache(ScriptLoadRequest* aRequest, return; } +void ScriptLoader::StoreCacheInfo(LoadedScript* aLoadedScript, + ScriptLoadRequest* aRequest) { + MOZ_ASSERT(aRequest->mCacheInfo); + MOZ_ASSERT(!aRequest->SRIAndBytecode().empty()); + MOZ_ASSERT(aRequest->SRIAndBytecode().length() == aRequest->GetSRILength()); + MOZ_ASSERT(!aLoadedScript->mCacheInfo); + MOZ_ASSERT(aLoadedScript->mSRI.empty()); + + if (!aLoadedScript->mSRI.appendAll(aRequest->SRIAndBytecode())) { + return; + } + + aLoadedScript->mCacheInfo = aRequest->mCacheInfo; +} + void ScriptLoader::EmulateNetworkEvents(ScriptLoadRequest* aRequest) { - MOZ_ASSERT(aRequest->IsCachedStencil()); + MOZ_ASSERT(aRequest->IsStencil()); MOZ_ASSERT(aRequest->mNetworkMetadata); nsIScriptElement* element = aRequest->GetScriptLoadContext()->mScriptElement; @@ -1438,7 +1430,7 @@ bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement, return block; } - if (request->IsCachedStencil()) { + if (request->IsStencil()) { // https://html.spec.whatwg.org/#prepare-the-script-element // // Step 33. If el's type is "classic" and el has a src attribute, or el's @@ -1889,7 +1881,7 @@ nsresult ScriptLoader::CompileOffThreadOrProcessRequest( NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "Processing requests when running scripts is unsafe."); - if (!aRequest->IsCachedStencil() && + if (!aRequest->IsStencil() && !aRequest->GetScriptLoadContext()->mCompileOrDecodeTask && !aRequest->GetScriptLoadContext()->CompileStarted()) { bool couldCompile = false; @@ -2749,13 +2741,28 @@ void ScriptLoader::CalculateCacheFlag(ScriptLoadRequest* aRequest) { // We need the nsICacheInfoChannel to exist to be able to open the alternate // data output stream. - if (!aRequest->getLoadedScript()->HasDiskCacheReference()) { - LOG( - ("ScriptLoadRequest (%p): Bytecode-cache: Skip disk: " - "!LoadedScript::HasDiskCacheReference", - aRequest)); - aRequest->MarkSkippedDiskCaching(); - return; + if (aRequest->IsStencil()) { + // For in-memory cache, the pointer is cached in the LoadedScript, + // if the cache had never been saved. + if (!aRequest->getLoadedScript()->mCacheInfo) { + LOG( + ("ScriptLoadRequest (%p): Bytecode-cache: Skip disk: " + "!LoadedScript::mCacheInfo", + aRequest)); + aRequest->MarkSkippedDiskCaching(); + return; + } + } else { + // This pointer would only be non-null if the bytecode was + // activated at the time the channel got created in StartLoad. + if (!aRequest->HasDiskCacheReference()) { + LOG( + ("ScriptLoadRequest (%p): Bytecode-cache: Skip disk: " + "!HasDiskCacheReference", + aRequest)); + aRequest->MarkSkippedDiskCaching(); + return; + } } // Look at the preference to know which strategy (parameters) should be used @@ -2813,7 +2820,7 @@ void ScriptLoader::CalculateCacheFlag(ScriptLoadRequest* aRequest) { if (hasSourceLengthMin) { size_t sourceLength; size_t minLength; - if (aRequest->IsCachedStencil()) { + if (aRequest->IsStencil()) { sourceLength = JS::GetScriptSourceLength(aRequest->GetStencil()); } else { MOZ_ASSERT(aRequest->IsTextSource()); @@ -2835,12 +2842,11 @@ void ScriptLoader::CalculateCacheFlag(ScriptLoadRequest* aRequest) { // are going to be dropped soon. if (hasFetchCountMin) { uint32_t fetchCount = 0; - if (aRequest->IsCachedStencil()) { + if (aRequest->IsStencil()) { fetchCount = aRequest->mLoadedScript->mFetchCount; } else { if (NS_FAILED( - aRequest->getLoadedScript()->mCacheInfo->GetCacheTokenFetchCount( - &fetchCount))) { + aRequest->mCacheInfo->GetCacheTokenFetchCount(&fetchCount))) { LOG( ("ScriptLoadRequest (%p): Bytecode-cache: Skip disk: Cannot get " "fetchCount.", @@ -3059,6 +3065,7 @@ static void InstantiateStencil( void ScriptLoader::InstantiateClassicScriptFromMaybeEncodedSource( JSContext* aCx, JS::CompileOptions& aCompileOptions, ScriptLoadRequest* aRequest, JS::MutableHandle<JSScript*> aScript, + RefPtr<JS::Stencil>& aStencilOut, JS::Handle<JS::Value> aDebuggerPrivateValue, JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv) { nsAutoCString profilerLabelString; @@ -3079,8 +3086,7 @@ void ScriptLoader::InstantiateClassicScriptFromMaybeEncodedSource( aRv.NoteJSContextException(aCx); return; } - - aRequest->SetStencil(stencil); + aStencilOut = stencil.get(); InstantiateStencil(aCx, aCompileOptions, stencil, aScript, aDebuggerPrivateValue, aDebuggerIntroductionScript, @@ -3093,10 +3099,9 @@ void ScriptLoader::InstantiateClassicScriptFromMaybeEncodedSource( RefPtr<JS::Stencil> stencil; Decode(aCx, aCompileOptions, aRequest->Bytecode(), stencil, aRv); + aStencilOut = stencil.get(); if (stencil) { - aRequest->SetStencil(stencil); - InstantiateStencil(aCx, aCompileOptions, stencil, aScript, aDebuggerPrivateValue, aDebuggerIntroductionScript, aRv); @@ -3105,7 +3110,7 @@ void ScriptLoader::InstantiateClassicScriptFromMaybeEncodedSource( // We do not expect to be saving anything when we already have some // bytecode. - MOZ_ASSERT(!aRequest->getLoadedScript()->HasDiskCacheReference()); + MOZ_ASSERT(!aRequest->HasDiskCacheReference()); return; } @@ -3130,8 +3135,7 @@ void ScriptLoader::InstantiateClassicScriptFromMaybeEncodedSource( aRv.NoteJSContextException(aCx); return; } - - aRequest->SetStencil(stencil); + aStencilOut = stencil.get(); InstantiateStencil(aCx, aCompileOptions, stencil, aScript, aDebuggerPrivateValue, aDebuggerIntroductionScript, aRv, @@ -3159,10 +3163,9 @@ void ScriptLoader::InstantiateClassicScriptFromMaybeEncodedSource( MOZ_ASSERT(!maybeSource.empty()); maybeSource.mapNonEmpty(compile); + aStencilOut = stencil.get(); if (stencil) { - aRequest->SetStencil(stencil); - InstantiateStencil(aCx, aCompileOptions, stencil, aScript, aDebuggerPrivateValue, aDebuggerIntroductionScript, erv, /* aStorage = */ nullptr, @@ -3211,7 +3214,7 @@ void ScriptLoader::InstantiateClassicScriptFromAny( ScriptLoadRequest* aRequest, JS::MutableHandle<JSScript*> aScript, JS::Handle<JS::Value> aDebuggerPrivateValue, JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv) { - if (aRequest->IsCachedStencil()) { + if (aRequest->IsStencil()) { RefPtr<JS::Stencil> stencil = aRequest->GetStencil(); InstantiateClassicScriptFromCachedStencil( aCx, aCompileOptions, aRequest, stencil, aScript, aDebuggerPrivateValue, @@ -3219,14 +3222,15 @@ void ScriptLoader::InstantiateClassicScriptFromAny( return; } + RefPtr<JS::Stencil> stencil; InstantiateClassicScriptFromMaybeEncodedSource( - aCx, aCompileOptions, aRequest, aScript, aDebuggerPrivateValue, + aCx, aCompileOptions, aRequest, aScript, stencil, aDebuggerPrivateValue, aDebuggerIntroductionScript, aRv); if (aRv.Failed()) { return; } - TryCacheRequest(aRequest); + TryCacheRequest(aRequest, stencil); } ScriptLoader::CacheBehavior ScriptLoader::GetCacheBehavior( @@ -3272,8 +3276,8 @@ ScriptLoader::CacheBehavior ScriptLoader::GetCacheBehavior( return CacheBehavior::Insert; } -void ScriptLoader::TryCacheRequest(ScriptLoadRequest* aRequest) { - MOZ_ASSERT(aRequest->HasStencil()); +void ScriptLoader::TryCacheRequest(ScriptLoadRequest* aRequest, + RefPtr<JS::Stencil>& aStencil) { CacheBehavior cacheBehavior = GetCacheBehavior(aRequest); if (cacheBehavior == CacheBehavior::DoNothing) { @@ -3281,17 +3285,21 @@ void ScriptLoader::TryCacheRequest(ScriptLoadRequest* aRequest) { } MOZ_ASSERT(mCache); + MOZ_ASSERT(aStencil); - if (!JS::IsStencilCacheable(aRequest->GetStencil())) { + if (!JS::IsStencilCacheable(aStencil)) { // If the stencil is not compatible with the cache (e.g. contains asm.js), // this should also evict any the existing cache if any. cacheBehavior = CacheBehavior::Evict; } - aRequest->ConvertToCachedStencil(); + aRequest->SetStencil(aStencil.forget()); if (cacheBehavior == CacheBehavior::Insert) { auto loadData = MakeRefPtr<ScriptLoadData>(this, aRequest); + if (aRequest->HasDiskCacheReference()) { + StoreCacheInfo(aRequest->getLoadedScript(), aRequest); + } mCache->Insert(*loadData); LOG(("ScriptLoader (%p): Inserting in-memory cache for %s.", this, aRequest->mURI->GetSpecOrDefault().get())); @@ -3312,18 +3320,18 @@ nsCString& ScriptLoader::BytecodeMimeTypeFor(ScriptLoadRequest* aRequest) { return nsContentUtils::JSScriptBytecodeMimeType(); } -/* static */ -nsCString& ScriptLoader::BytecodeMimeTypeFor( - JS::loader::LoadedScript* aLoadedScript) { - if (aLoadedScript->IsModuleScript()) { - return nsContentUtils::JSModuleBytecodeMimeType(); +void ScriptLoader::MaybePrepareForCacheBeforeExecute( + ScriptLoadRequest* aRequest, JS::Handle<JSScript*> aScript) { + if (!aRequest->PassedConditionForEitherCache()) { + return; } - return nsContentUtils::JSScriptBytecodeMimeType(); + + aRequest->MarkScriptForCache(aScript); } nsresult ScriptLoader::MaybePrepareForCacheAfterExecute( ScriptLoadRequest* aRequest, nsresult aRv) { - if (aRequest->PassedConditionForEitherCache()) { + if (aRequest->IsMarkedForEitherCache()) { TRACE_FOR_TEST(aRequest, "scriptloader_encode"); // Bytecode-encoding branch is used for 2 purposes right now: // * If the request is stencil, reflect delazifications to cached stencil @@ -3335,7 +3343,7 @@ nsresult ScriptLoader::MaybePrepareForCacheAfterExecute( // NOTE: This assertion will fail once we start encoding more data after the // first encode. MOZ_ASSERT_IF( - !aRequest->IsCachedStencil(), + !aRequest->IsStencil(), aRequest->GetSRILength() == aRequest->SRIAndBytecode().length()); RegisterForCache(aRequest); @@ -3345,11 +3353,35 @@ nsresult ScriptLoader::MaybePrepareForCacheAfterExecute( LOG(("ScriptLoadRequest (%p): Bytecode-cache: disabled (rv = %X)", aRequest, unsigned(aRv))); TRACE_FOR_TEST_NONE(aRequest, "scriptloader_no_encode"); - aRequest->getLoadedScript()->DropDiskCacheReference(); + aRequest->DropDiskCacheReference(); return aRv; } +void ScriptLoader::MaybePrepareModuleForCacheBeforeExecute( + JSContext* aCx, ModuleLoadRequest* aRequest) { + if (aRequest->IsMarkedForEitherCache()) { + // This module is imported multiple times, and already marked. + return; + } + + if (aRequest->PassedConditionForEitherCache()) { + aRequest->MarkModuleForCache(); + } + + for (auto* r = mCacheableDependencyModules.getFirst(); r; r = r->getNext()) { + auto* dep = r->AsModuleRequest(); + MOZ_ASSERT(dep->PassedConditionForEitherCache()); + + if (dep->GetRootModule() != aRequest) { + continue; + } + MOZ_ASSERT(!dep->IsMarkedForEitherCache()); + + dep->MarkModuleForCache(); + } +} + nsresult ScriptLoader::MaybePrepareModuleForCacheAfterExecute( ModuleLoadRequest* aRequest, nsresult aRv) { MOZ_ASSERT(aRequest->IsTopLevel()); @@ -3406,7 +3438,7 @@ nsresult ScriptLoader::EvaluateScript(nsIGlobalObject* aGlobalObject, } #endif - if (aRequest->IsCachedStencil()) { + if (aRequest->IsStencil()) { #ifdef DEBUG // A request with cache might not have mBaseURL. if (aRequest->mBaseURL) { @@ -3465,13 +3497,17 @@ nsresult ScriptLoader::EvaluateScript(nsIGlobalObject* aGlobalObject, classicScriptValue, introductionScript, erv); if (!erv.Failed()) { - LOG(("ScriptLoadRequest (%p): Evaluate Script", aRequest)); - AUTO_PROFILER_MARKER_TEXT("ScriptExecution", JS, - MarkerInnerWindowIdFromJSContext(cx), - profilerLabelString); + MaybePrepareForCacheBeforeExecute(aRequest, script); + + { + LOG(("ScriptLoadRequest (%p): Evaluate Script", aRequest)); + AUTO_PROFILER_MARKER_TEXT("ScriptExecution", JS, + MarkerInnerWindowIdFromJSContext(cx), + profilerLabelString); - MOZ_ASSERT(options.noScriptRval); - ExecuteCompiledScript(cx, classicScript, script, erv); + MOZ_ASSERT(options.noScriptRval); + ExecuteCompiledScript(cx, classicScript, script, erv); + } } rv = EvaluationExceptionToNSResult(erv); @@ -3503,9 +3539,9 @@ LoadedScript* ScriptLoader::GetActiveScript(JSContext* aCx) { } void ScriptLoader::RegisterForCache(ScriptLoadRequest* aRequest) { - MOZ_ASSERT(aRequest->PassedConditionForEitherCache()); - MOZ_ASSERT_IF(aRequest->PassedConditionForDiskCache(), - aRequest->getLoadedScript()->HasDiskCacheReference()); + MOZ_ASSERT(aRequest->IsMarkedForEitherCache()); + MOZ_ASSERT_IF(aRequest->IsMarkedForDiskCache(), + aRequest->HasDiskCacheReference()); MOZ_DIAGNOSTIC_ASSERT(!aRequest->isInList()); mCachingQueue.AppendElement(aRequest); } @@ -3580,61 +3616,135 @@ void ScriptLoader::UpdateCache() { return; } - JS::FrontendContext* fc = JS::NewFrontendContext(); - if (!fc) { - LOG( - ("ScriptLoader (%p): Cannot create FrontendContext for bytecode " - "encoding.", - this)); + // Should not be encoding modules at all. + nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject(); + if (!globalObject) { + GiveUpCaching(); + return; + } + + nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext(); + if (!context) { + GiveUpCaching(); return; } + AutoEntryScript aes(globalObject, "encode bytecode", true); RefPtr<ScriptLoadRequest> request; while (!mCachingQueue.isEmpty()) { request = mCachingQueue.StealFirst(); MOZ_ASSERT(!IsWebExtensionRequest(request), "Bytecode for web extension content scrips is not cached"); - // The bytecode encoding is performed only when there was no - // bytecode stored in the necko cache. - // - // TODO: Move this to SharedScriptCache. - if (request->PassedConditionForDiskCache() && - request->getLoadedScript()->HasDiskCacheReference()) { - EncodeBytecodeAndSave(fc, request->getLoadedScript()); - } + RefPtr<JS::Stencil> stencil; + stencil = FinishCollectingDelazifications(aes.cx(), request); + if (mCache) { + if (stencil) { + MOZ_ASSERT_IF(request->IsStencil(), stencil == request->GetStencil()); + + // The bytecode encoding is performed only when there was no + // bytecode stored in the necko cache. + // + // TODO: Move this to SharedScriptCache. + + if (request->IsMarkedForDiskCache()) { + if (request->HasDiskCacheReference()) { + // The nsICacheInfoChannel is stored when the this request + // receives a source text (See ScriptLoadHandler::OnStreamComplete), + // and also the SRI is calculated only in that case. + MOZ_ASSERT(request->SRIAndBytecode().length() == + request->GetSRILength()); + + EncodeBytecodeAndSave(aes.cx(), request, request->mCacheInfo, + BytecodeMimeTypeFor(request), + request->SRIAndBytecode(), stencil); + + request->DropBytecode(); + } else if (request->getLoadedScript()->mCacheInfo) { + // The nsICacheInfoChannel is stored when the cached request + // received a source text (See ScriptLoadHandler::OnStreamComplete), + // and also the SRI is calculated and stored into the cache only in + // that case. + EncodeBytecodeAndSave(aes.cx(), request, + request->getLoadedScript()->mCacheInfo, + BytecodeMimeTypeFor(request), + request->getLoadedScript()->mSRI, stencil); + + // The cached nsICacheInfoChannel can be used only once. + // We don't overwrite the bytecode cache. + request->getLoadedScript()->mCacheInfo = nullptr; + request->getLoadedScript()->mSRI.clear(); + } + } + } + request->DropDiskCacheReference(); + } else { + if (stencil) { + MOZ_ASSERT(request->SRIAndBytecode().length() == + request->GetSRILength()); + + EncodeBytecodeAndSave(aes.cx(), request, request->mCacheInfo, + BytecodeMimeTypeFor(request), + request->SRIAndBytecode(), stencil); - request->DropBytecode(); - request->getLoadedScript()->DropDiskCacheReference(); + request->DropBytecode(); + } + request->DropDiskCacheReference(); + } } +} - JS::DestroyFrontendContext(fc); +already_AddRefed<JS::Stencil> ScriptLoader::FinishCollectingDelazifications( + JSContext* aCx, ScriptLoadRequest* aRequest) { + RefPtr<JS::Stencil> stencil; + bool result; + if (aRequest->IsModuleRequest()) { + aRequest->mScriptForCache = nullptr; + ModuleScript* moduleScript = aRequest->AsModuleRequest()->mModuleScript; + JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord()); + result = JS::FinishCollectingDelazifications(aCx, module, + getter_AddRefs(stencil)); + } else { + JS::Rooted<JSScript*> script(aCx, aRequest->mScriptForCache); + aRequest->mScriptForCache = nullptr; + result = JS::FinishCollectingDelazifications(aCx, script, + getter_AddRefs(stencil)); + } + if (!result) { + JS_ClearPendingException(aCx); + return nullptr; + } + return stencil.forget(); } void ScriptLoader::EncodeBytecodeAndSave( - JS::FrontendContext* aFc, JS::loader::LoadedScript* aLoadedScript) { - MOZ_ASSERT(aLoadedScript->HasDiskCacheReference()); - MOZ_ASSERT(aLoadedScript->HasStencil()); + JSContext* aCx, ScriptLoadRequest* aRequest, + nsCOMPtr<nsICacheInfoChannel>& aCacheInfo, nsCString& aMimeType, + const JS::TranscodeBuffer& aSRI, JS::Stencil* aStencil) { + MOZ_ASSERT(aCacheInfo); - size_t SRILength = aLoadedScript->SRIAndBytecode().length(); + using namespace mozilla::Telemetry; + nsresult rv = NS_OK; + auto bytecodeFailed = mozilla::MakeScopeExit( + [&]() { TRACE_FOR_TEST_NONE(aRequest, "scriptloader_bytecode_failed"); }); + + size_t SRILength = aSRI.length(); MOZ_ASSERT(JS::IsTranscodingBytecodeOffsetAligned(SRILength)); JS::TranscodeBuffer SRIAndBytecode; - if (!SRIAndBytecode.appendAll(aLoadedScript->SRIAndBytecode())) { - LOG(("LoadedScript (%p): Cannot allocate buffer", aLoadedScript)); + if (!SRIAndBytecode.appendAll(aSRI)) { + LOG(("ScriptLoadRequest (%p): Cannot allocate buffer", aRequest)); return; } - JS::TranscodeResult result = - JS::EncodeStencil(aFc, aLoadedScript->GetStencil(), SRIAndBytecode); - + JS::TranscodeResult result = JS::EncodeStencil(aCx, aStencil, SRIAndBytecode); if (result != JS::TranscodeResult::Ok) { // Encoding can be aborted for non-supported syntax (e.g. asm.js), or // any other internal error. // We don't care the error and just give up encoding. - JS::ClearFrontendErrors(aFc); + JS_ClearPendingException(aCx); - LOG(("LoadedScript (%p): Cannot serialize bytecode", aLoadedScript)); + LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", aRequest)); return; } @@ -3646,9 +3756,9 @@ void ScriptLoader::EncodeBytecodeAndSave( if (compressedBytecode.length() >= UINT32_MAX) { LOG( - ("LoadedScript (%p): Bytecode cache is too large to be decoded " + ("ScriptLoadRequest (%p): Bytecode cache is too large to be decoded " "correctly.", - aLoadedScript)); + aRequest)); return; } @@ -3656,36 +3766,38 @@ void ScriptLoader::EncodeBytecodeAndSave( // might fail if the stream is already open by another request, in which // case, we just ignore the current one. nsCOMPtr<nsIAsyncOutputStream> output; - nsresult rv = aLoadedScript->mCacheInfo->OpenAlternativeOutputStream( - BytecodeMimeTypeFor(aLoadedScript), - static_cast<int64_t>(compressedBytecode.length()), + rv = aCacheInfo->OpenAlternativeOutputStream( + aMimeType, static_cast<int64_t>(compressedBytecode.length()), getter_AddRefs(output)); if (NS_FAILED(rv)) { LOG( - ("LoadedScript (%p): Cannot open bytecode cache (rv = %X, output " + ("ScriptLoadRequest (%p): Cannot open bytecode cache (rv = %X, output " "= %p)", - aLoadedScript, unsigned(rv), output.get())); + aRequest, unsigned(rv), output.get())); return; } MOZ_ASSERT(output); auto closeOutStream = mozilla::MakeScopeExit([&]() { rv = output->CloseWithStatus(rv); - LOG(("LoadedScript (%p): Closing (rv = %X)", aLoadedScript, unsigned(rv))); + LOG(("ScriptLoadRequest (%p): Closing (rv = %X)", aRequest, unsigned(rv))); }); uint32_t n; rv = output->Write(reinterpret_cast<char*>(compressedBytecode.begin()), compressedBytecode.length(), &n); LOG( - ("LoadedScript (%p): Write bytecode cache (rv = %X, length = %u, " + ("ScriptLoadRequest (%p): Write bytecode cache (rv = %X, length = %u, " "written = %u)", - aLoadedScript, unsigned(rv), unsigned(compressedBytecode.length()), n)); + aRequest, unsigned(rv), unsigned(compressedBytecode.length()), n)); if (NS_FAILED(rv)) { return; } MOZ_RELEASE_ASSERT(compressedBytecode.length() == n); + + bytecodeFailed.release(); + TRACE_FOR_TEST_NONE(aRequest, "scriptloader_bytecode_saved"); } void ScriptLoader::GiveUpCaching() { @@ -3693,13 +3805,40 @@ void ScriptLoader::GiveUpCaching() { // to avoid queuing more scripts. mGiveUpCaching = true; + // Ideally we prefer to properly end the incremental encoder, such that we + // would not keep a large buffer around. If we cannot, we fallback on the + // removal of all request from the current list and these large buffers would + // be removed at the same time as the source object. + nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject(); + AutoAllowLegacyScriptExecution exemption; + Maybe<AutoEntryScript> aes; + + if (globalObject) { + nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext(); + if (context) { + aes.emplace(globalObject, "give-up bytecode encoding", true); + } + } + while (!mCachingQueue.isEmpty()) { RefPtr<ScriptLoadRequest> request = mCachingQueue.StealFirst(); LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", request.get())); TRACE_FOR_TEST_NONE(request, "scriptloader_bytecode_failed"); MOZ_ASSERT(!IsWebExtensionRequest(request)); - request->getLoadedScript()->DropDiskCacheReference(); + if (aes.isSome()) { + if (request->IsModuleRequest()) { + ModuleScript* moduleScript = request->AsModuleRequest()->mModuleScript; + JS::Rooted<JSObject*> module(aes->cx(), moduleScript->ModuleRecord()); + JS::AbortCollectingDelazifications(module); + } else { + JS::Rooted<JSScript*> script(aes->cx(), request->mScriptForCache); + request->mScriptForCache = nullptr; + JS::AbortCollectingDelazifications(script); + } + } + + request->DropDiskCacheReference(); } while (!mCacheableDependencyModules.isEmpty()) { diff --git a/dom/script/ScriptLoader.h b/dom/script/ScriptLoader.h @@ -10,8 +10,7 @@ #include "ModuleLoader.h" #include "SharedScriptCache.h" #include "js/TypeDecls.h" -#include "js/Utility.h" // JS::FreePolicy -#include "js/experimental/CompileScript.h" // JS::FrontendContext +#include "js/Utility.h" // JS::FreePolicy #include "js/loader/LoadedScript.h" #include "js/loader/ModuleLoaderBase.h" #include "js/loader/ScriptKind.h" @@ -527,7 +526,8 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface { CacheBehavior GetCacheBehavior(ScriptLoadRequest* aRequest); - void TryCacheRequest(ScriptLoadRequest* aRequest); + void TryCacheRequest(ScriptLoadRequest* aRequest, + RefPtr<JS::Stencil>& aStencil); JS::loader::ScriptLoadRequest* LookupPreloadRequest( nsIScriptElement* aElement, JS::loader::ScriptKind aScriptKind, @@ -695,6 +695,7 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface { void InstantiateClassicScriptFromMaybeEncodedSource( JSContext* aCx, JS::CompileOptions& aCompileOptions, ScriptLoadRequest* aRequest, JS::MutableHandle<JSScript*> aScript, + RefPtr<JS::Stencil>& aStencilOut, JS::Handle<JS::Value> aDebuggerPrivateValue, JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv); @@ -708,8 +709,13 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface { JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv); static nsCString& BytecodeMimeTypeFor(ScriptLoadRequest* aRequest); - static nsCString& BytecodeMimeTypeFor( - JS::loader::LoadedScript* aLoadedScript); + + // Decide whether to encode bytecode for given script load request, + // and store the script into the request if necessary. + // + // This method must be called before executing the script. + void MaybePrepareForCacheBeforeExecute(ScriptLoadRequest* aRequest, + JS::Handle<JSScript*> aScript); // Queue the script load request for caching if we decided to cache it, or // cleanup the script load request fields otherwise. @@ -718,6 +724,9 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface { nsresult MaybePrepareForCacheAfterExecute(ScriptLoadRequest* aRequest, nsresult aRv); + void MaybePrepareModuleForCacheBeforeExecute( + JSContext* aCx, ModuleLoadRequest* aRequest) override; + // Queue the top-level module load request for caching if we decided to cache // it, or cleanup the module load request fields otherwise. // @@ -763,10 +772,22 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface { void UpdateCache(); /** + * Finish collecting the delazifications and return the stencil. + */ + already_AddRefed<JS::Stencil> FinishCollectingDelazifications( + JSContext* aCx, ScriptLoadRequest* aRequest); + + /** * Encode the stencils and save the bytecode to the necko cache. */ - void EncodeBytecodeAndSave(JS::FrontendContext* aFc, - JS::loader::LoadedScript* aLoadedScript); + void EncodeBytecodeAndSave(JSContext* aCx, ScriptLoadRequest* aRequest, + nsCOMPtr<nsICacheInfoChannel>& aCacheInfo, + nsCString& aMimeType, + const JS::TranscodeBuffer& aSRI, + JS::Stencil* aStencil); + + void StoreCacheInfo(JS::loader::LoadedScript* aLoadedScript, + ScriptLoadRequest* aRequest); /** * Stop collecting delazifications for all requests. diff --git a/dom/xul/nsXULElement.cpp b/dom/xul/nsXULElement.cpp @@ -1456,21 +1456,15 @@ nsXULPrototypeScript::nsXULPrototypeScript(uint32_t aLineNo) mSrcLoadWaiters(nullptr), mStencil(nullptr) {} -static nsresult WriteStencil(nsIObjectOutputStream* aStream, +static nsresult WriteStencil(nsIObjectOutputStream* aStream, JSContext* aCx, JS::Stencil* aStencil) { - JS::FrontendContext* fc = JS::NewFrontendContext(); - if (!fc) { - return NS_ERROR_OUT_OF_MEMORY; - } - JS::TranscodeBuffer buffer; JS::TranscodeResult code; - code = JS::EncodeStencil(fc, aStencil, buffer); - - JS::DestroyFrontendContext(fc); + code = JS::EncodeStencil(aCx, aStencil, buffer); if (code != JS::TranscodeResult::Ok) { if (code == JS::TranscodeResult::Throw) { + JS_ClearPendingException(aCx); return NS_ERROR_OUT_OF_MEMORY; } @@ -1563,6 +1557,11 @@ nsresult nsXULPrototypeScript::Serialize( const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) { NS_ENSURE_TRUE(aProtoDoc, NS_ERROR_UNEXPECTED); + AutoJSAPI jsapi; + if (!jsapi.Init(xpc::CompilationScope())) { + return NS_ERROR_UNEXPECTED; + } + NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil, "script source still loading when serializing?!"); if (!mStencil) return NS_ERROR_FAILURE; @@ -1572,7 +1571,10 @@ nsresult nsXULPrototypeScript::Serialize( rv = aStream->Write32(mLineNo); if (NS_FAILED(rv)) return rv; - return WriteStencil(aStream, mStencil); + JSContext* cx = jsapi.cx(); + MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx)); + + return WriteStencil(aStream, cx, mStencil); } nsresult nsXULPrototypeScript::SerializeOutOfLine( diff --git a/js/loader/LoadedScript.cpp b/js/loader/LoadedScript.cpp @@ -49,7 +49,7 @@ LoadedScript::LoadedScript(ScriptKind aKind, } LoadedScript::LoadedScript(const LoadedScript& aOther) - : mDataType(DataType::eCachedStencil), + : mDataType(DataType::eStencil), mKind(aOther.mKind), mReferrerPolicy(aOther.mReferrerPolicy), mBytecodeOffset(0), @@ -62,10 +62,10 @@ LoadedScript::LoadedScript(const LoadedScript& aOther) MOZ_ASSERT(mURI); // NOTE: This is only for the stencil case. // The script text and the bytecode are not reflected. - MOZ_DIAGNOSTIC_ASSERT(aOther.mDataType == DataType::eCachedStencil); + MOZ_DIAGNOSTIC_ASSERT(aOther.mDataType == DataType::eStencil); MOZ_DIAGNOSTIC_ASSERT(mStencil); MOZ_ASSERT(!mScriptData); - MOZ_ASSERT(mSRIAndBytecode.empty()); + MOZ_ASSERT(mScriptBytecode.empty()); } LoadedScript::~LoadedScript() { @@ -118,7 +118,7 @@ size_t LoadedScript::SizeOfIncludingThis( } } - bytes += mSRIAndBytecode.sizeOfExcludingThis(aMallocSizeOf); + bytes += mScriptBytecode.sizeOfExcludingThis(aMallocSizeOf); // NOTE: Stencil is reported by SpiderMonkey. return bytes; @@ -262,7 +262,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ModuleScript, LoadedScript) tmp->UnlinkModuleRecord(); tmp->mParseError.setUndefined(); tmp->mErrorToRethrow.setUndefined(); - tmp->DropDiskCacheReference(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ModuleScript, LoadedScript) @@ -292,13 +291,13 @@ ModuleScript::ModuleScript(const LoadedScript& aOther) : LoadedScript(aOther) { already_AddRefed<ModuleScript> ModuleScript::FromCache( const LoadedScript& aScript) { MOZ_DIAGNOSTIC_ASSERT(aScript.IsModuleScript()); - MOZ_DIAGNOSTIC_ASSERT(aScript.IsCachedStencil()); + MOZ_DIAGNOSTIC_ASSERT(aScript.IsStencil()); return mozilla::MakeRefPtr<ModuleScript>(aScript).forget(); } already_AddRefed<LoadedScript> ModuleScript::ToCache() { - MOZ_DIAGNOSTIC_ASSERT(IsCachedStencil()); + MOZ_DIAGNOSTIC_ASSERT(IsStencil()); MOZ_DIAGNOSTIC_ASSERT(!HasParseError()); MOZ_DIAGNOSTIC_ASSERT(!HasErrorToRethrow()); diff --git a/js/loader/LoadedScript.h b/js/loader/LoadedScript.h @@ -117,26 +117,7 @@ class LoadedScript : public nsIMemoryReporter { // Type of data this instance holds, which is either provided by the nsChannel // or retrieved from the cache. - enum class DataType : uint8_t { - // This script haven't yet received the data. - eUnknown, - - // This script is received as a plain text from the channel. - // mScriptData holds the text source, and mStencil holds the compiled - // stencil. - // mSRIAndBytecode holds the SRI. - eTextSource, - - // This script is received as a bytecode from the channel. - // mSRIAndBytecode holds the SRI and the bytecode, and mStencil holds the - // decoded stencil. - eBytecode, - - // This script is cached from the previous load. - // mStencil holds the cached stencil, and mSRIAndBytecode holds the SRI. - // mScriptData is unused. - eCachedStencil - }; + enum class DataType : uint8_t { eUnknown, eTextSource, eBytecode, eStencil }; // Use a vector backed by the JS allocator for script text so that contents // can be transferred in constant time to the JS engine, not copied in linear @@ -151,7 +132,7 @@ class LoadedScript : public nsIMemoryReporter { bool IsTextSource() const { return mDataType == DataType::eTextSource; } bool IsSource() const { return IsTextSource(); } bool IsBytecode() const { return mDataType == DataType::eBytecode; } - bool IsCachedStencil() const { return mDataType == DataType::eCachedStencil; } + bool IsStencil() const { return mDataType == DataType::eStencil; } void SetUnknownDataType() { mDataType = DataType::eUnknown; @@ -169,10 +150,10 @@ class LoadedScript : public nsIMemoryReporter { mDataType = DataType::eBytecode; } - void ConvertToCachedStencil() { - MOZ_ASSERT(HasStencil()); + void SetStencil(already_AddRefed<Stencil> aStencil) { SetUnknownDataType(); - mDataType = DataType::eCachedStencil; + mDataType = DataType::eStencil; + mStencil = aStencil; } bool IsUTF16Text() const { @@ -223,7 +204,7 @@ class LoadedScript : public nsIMemoryReporter { } bool CanHaveBytecode() const { - return IsBytecode() || IsSource() || IsCachedStencil(); + return IsBytecode() || IsSource() || IsStencil(); } TranscodeBuffer& SRIAndBytecode() { @@ -231,11 +212,11 @@ class LoadedScript : public nsIMemoryReporter { // as we want to be able to save the bytecode content when we are loading // from source. MOZ_ASSERT(CanHaveBytecode()); - return mSRIAndBytecode; + return mScriptBytecode; } TranscodeRange Bytecode() const { MOZ_ASSERT(IsBytecode()); - const auto& bytecode = mSRIAndBytecode; + const auto& bytecode = mScriptBytecode; auto offset = mBytecodeOffset; return TranscodeRange(bytecode.begin() + offset, bytecode.length() - offset); @@ -252,35 +233,18 @@ class LoadedScript : public nsIMemoryReporter { void DropBytecode() { MOZ_ASSERT(CanHaveBytecode()); - mSRIAndBytecode.clearAndFree(); + mScriptBytecode.clearAndFree(); } - bool HasStencil() const { return mStencil; } - Stencil* GetStencil() const { - MOZ_ASSERT(!IsUnknownDataType()); - MOZ_ASSERT(HasStencil()); + MOZ_ASSERT(IsStencil()); return mStencil; } - void SetStencil(Stencil* aStencil) { - MOZ_ASSERT(aStencil); - MOZ_ASSERT(!HasStencil()); - mStencil = aStencil; - } - - // Check the reference to the cache info channel, which is used by the disk - // cache. - bool HasDiskCacheReference() const { return !!mCacheInfo; } - - // Drop the reference to the cache info channel. - void DropDiskCacheReference() { mCacheInfo = nullptr; } - public: // Fields. - // Determine whether the mScriptData or mSRIAndBytecode is used. - // See DataType description for more info. + // Determine whether the mScriptData or mScriptBytecode is used. DataType mDataType; // The consumer-defined number of times that this loaded script is used. @@ -296,7 +260,7 @@ class LoadedScript : public nsIMemoryReporter { mozilla::dom::ReferrerPolicy mReferrerPolicy; public: - // Offset of the bytecode in mSRIAndBytecode. + // Offset of the bytecode in mScriptBytecode. uint32_t mBytecodeOffset; private: @@ -314,23 +278,18 @@ class LoadedScript : public nsIMemoryReporter { // since mScriptData is cleared when the source is passed to the JS engine. size_t mReceivedScriptTextLength; - // Holds either of the following for non-inline scripts: - // * The SRI serialized hash and the paddings, which is calculated when - // receiving the source text - // * The SRI, padding, and the script bytecode, which is received - // from necko. The data is laid out according to ScriptBytecodeDataLayout - // or, if compression is enabled, ScriptBytecodeCompressedDataLayout. - TranscodeBuffer mSRIAndBytecode; + // Holds the SRI serialized hash and the script bytecode for non-inline + // scripts. The data is laid out according to ScriptBytecodeDataLayout + // or, if compression is enabled, ScriptBytecodeCompressedDataLayout. + TranscodeBuffer mScriptBytecode; - // Holds the stencil for the script. This field is used in all DataType. RefPtr<Stencil> mStencil; // The cache info channel used when saving the bytecode to the necko cache. - // - // This field is populated if the cache is enabled and this is either - // IsTextSource() or IsCachedStencil(), and it's cleared after saving the - // bytecode (Thus, used only once). nsCOMPtr<nsICacheInfoChannel> mCacheInfo; + + // The SRI data and the padding, used when saving the bytecode. + JS::TranscodeBuffer mSRI; }; // Provide accessors for any classes `Derived` which is providing the @@ -363,7 +322,7 @@ class LoadedScriptDelegate { bool IsTextSource() const { return GetLoadedScript()->IsTextSource(); } bool IsSource() const { return GetLoadedScript()->IsSource(); } bool IsBytecode() const { return GetLoadedScript()->IsBytecode(); } - bool IsCachedStencil() const { return GetLoadedScript()->IsCachedStencil(); } + bool IsStencil() const { return GetLoadedScript()->IsStencil(); } void SetUnknownDataType() { GetLoadedScript()->SetUnknownDataType(); } @@ -373,7 +332,9 @@ class LoadedScriptDelegate { void SetBytecode() { GetLoadedScript()->SetBytecode(); } - void ConvertToCachedStencil() { GetLoadedScript()->ConvertToCachedStencil(); } + void SetStencil(already_AddRefed<Stencil> aStencil) { + GetLoadedScript()->SetStencil(std::move(aStencil)); + } bool IsUTF16Text() const { return GetLoadedScript()->IsUTF16Text(); } bool IsUTF8Text() const { return GetLoadedScript()->IsUTF8Text(); } @@ -424,11 +385,7 @@ class LoadedScriptDelegate { void DropBytecode() { GetLoadedScript()->DropBytecode(); } - bool HasStencil() const { return GetLoadedScript()->HasStencil(); } Stencil* GetStencil() const { return GetLoadedScript()->GetStencil(); } - void SetStencil(Stencil* aStencil) { - GetLoadedScript()->SetStencil(aStencil); - } }; class ClassicScript final : public LoadedScript { diff --git a/js/loader/ModuleLoadRequest.cpp b/js/loader/ModuleLoadRequest.cpp @@ -61,14 +61,6 @@ ModuleLoadRequest::ModuleLoadRequest( MOZ_ASSERT(mLoader); } -ModuleLoadRequest::~ModuleLoadRequest() { - MOZ_ASSERT(!mReferrerScript); - MOZ_ASSERT(!mModuleRequestObj); - MOZ_ASSERT(mPayload.isUndefined()); - - DropJSObjects(this); -} - nsIGlobalObject* ModuleLoadRequest::GetGlobalObject() { return mLoader->GetGlobalObject(); } diff --git a/js/loader/ModuleLoadRequest.h b/js/loader/ModuleLoadRequest.h @@ -11,7 +11,6 @@ #include "ScriptLoadRequest.h" #include "ModuleLoaderBase.h" #include "mozilla/Assertions.h" -#include "mozilla/HoldDropJSObjects.h" #include "js/RootingAPI.h" #include "js/Value.h" #include "nsURIHashKey.h" @@ -28,7 +27,11 @@ class ModuleLoaderBase; // multiple imports of the same module. class ModuleLoadRequest final : public ScriptLoadRequest { - ~ModuleLoadRequest(); + ~ModuleLoadRequest() { + MOZ_ASSERT(!mReferrerScript); + MOZ_ASSERT(!mModuleRequestObj); + MOZ_ASSERT(mPayload.isUndefined()); + } ModuleLoadRequest(const ModuleLoadRequest& aOther) = delete; ModuleLoadRequest(ModuleLoadRequest&& aOther) = delete; @@ -83,6 +86,8 @@ class ModuleLoadRequest final : public ScriptLoadRequest { return mRootModule; } + void MarkModuleForCache() { MarkForCache(); } + // Convenience methods to call into the module loader for this request. void CancelDynamicImport(nsresult aResult) { diff --git a/js/loader/ModuleLoaderBase.cpp b/js/loader/ModuleLoaderBase.cpp @@ -543,11 +543,11 @@ nsresult ModuleLoaderBase::StartOrRestartModuleLoad(ModuleLoadRequest* aRequest, // NOTE: The LoadedScript::mDataType field used by the IsStencil call can be // modified asynchronously after the StartFetch call. // In order to avoid the race condition, cache the value here. - bool isCachedStencil = aRequest->IsCachedStencil(); + bool isStencil = aRequest->IsStencil(); - MOZ_ASSERT_IF(isCachedStencil, aRestart == RestartRequest::No); + MOZ_ASSERT_IF(isStencil, aRestart == RestartRequest::No); - if (!isCachedStencil) { + if (!isStencil) { aRequest->SetUnknownDataType(); } @@ -582,7 +582,7 @@ nsresult ModuleLoaderBase::StartOrRestartModuleLoad(ModuleLoadRequest* aRequest, rv = StartFetch(aRequest); NS_ENSURE_SUCCESS(rv, rv); - if (isCachedStencil) { + if (isStencil) { MOZ_ASSERT( IsModuleFetched(ModuleMapKey(aRequest->mURI, aRequest->mModuleType))); return NS_OK; @@ -1631,6 +1631,9 @@ nsresult ModuleLoaderBase::EvaluateModuleInContext( Rooted<Value> rval(aCx); + // TODO: Bug 1973321: Prepare Bytecode encoding for dynamic import + mLoader->MaybePrepareModuleForCacheBeforeExecute(aCx, aRequest); + bool ok = ModuleEvaluate(aCx, module, &rval); // ModuleEvaluate will usually set a pending exception if it returns false, @@ -1658,7 +1661,6 @@ nsresult ModuleLoaderBase::EvaluateModuleInContext( LOG(("ScriptLoadRequest (%p): evaluation failed on throw", aRequest)); } - // TODO: Bug 1973321: Prepare Bytecode encoding for dynamic import rv = mLoader->MaybePrepareModuleForCacheAfterExecute(aRequest, NS_OK); mLoader->MaybeUpdateCache(); diff --git a/js/loader/ScriptLoadRequest.cpp b/js/loader/ScriptLoadRequest.cpp @@ -11,6 +11,7 @@ #include "mozilla/dom/ScriptLoadContext.h" #include "mozilla/dom/WorkerLoadContext.h" #include "mozilla/dom/ScriptSettings.h" +#include "mozilla/HoldDropJSObjects.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/Unused.h" #include "mozilla/Utf8.h" // mozilla::Utf8Unit @@ -19,6 +20,7 @@ #include "ModuleLoadRequest.h" #include "nsContentUtils.h" +#include "nsICacheInfoChannel.h" #include "nsIClassOfService.h" #include "nsISupportsPriority.h" @@ -69,10 +71,22 @@ NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoadRequest) NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoadRequest) -NS_IMPL_CYCLE_COLLECTION(ScriptLoadRequest, mFetchOptions, mOriginPrincipal, - mBaseURL, mLoadedScript, mLoadContext) +NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptLoadRequest) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptLoadRequest) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchOptions, mOriginPrincipal, mBaseURL, + mLoadedScript, mCacheInfo, mLoadContext) + tmp->mScriptForCache = nullptr; + tmp->DropDiskCacheReference(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptLoadRequest) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions, mCacheInfo, mLoadContext, + mLoadedScript) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ScriptLoadRequest) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScriptForCache) NS_IMPL_CYCLE_COLLECTION_TRACE_END ScriptLoadRequest::ScriptLoadRequest( @@ -97,7 +111,7 @@ ScriptLoadRequest::ScriptLoadRequest( } } -ScriptLoadRequest::~ScriptLoadRequest() {} +ScriptLoadRequest::~ScriptLoadRequest() { DropJSObjects(this); } void ScriptLoadRequest::SetReady() { MOZ_ASSERT(!IsFinished()); @@ -111,6 +125,8 @@ void ScriptLoadRequest::Cancel() { } } +void ScriptLoadRequest::DropDiskCacheReference() { mCacheInfo = nullptr; } + bool ScriptLoadRequest::HasScriptLoadContext() const { return HasLoadContext() && mLoadContext->IsWindowContext(); } @@ -232,6 +248,14 @@ void ScriptLoadRequest::SetPendingFetchingError() { mState = State::PendingFetchingError; } +void ScriptLoadRequest::MarkScriptForCache(JSScript* aScript) { + MOZ_ASSERT(!IsModuleRequest()); + MOZ_ASSERT(!mScriptForCache); + MarkForCache(); + mScriptForCache = aScript; + HoldJSObjects(this); +} + static bool IsInternalURIScheme(nsIURI* uri) { return uri->SchemeIs("moz-extension") || uri->SchemeIs("resource") || uri->SchemeIs("moz-src") || uri->SchemeIs("chrome"); diff --git a/js/loader/ScriptLoadRequest.h b/js/loader/ScriptLoadRequest.h @@ -29,6 +29,8 @@ #include "ScriptKind.h" #include "ScriptFetchOptions.h" +class nsICacheInfoChannel; + namespace mozilla::dom { class ScriptLoadContext; @@ -187,11 +189,13 @@ class ScriptLoadRequest : public nsISupports, void SetPendingFetchingError(); bool PassedConditionForDiskCache() const { - return mDiskCachingPlan == CachingPlan::PassedCondition; + return mDiskCachingPlan == CachingPlan::PassedCondition || + mDiskCachingPlan == CachingPlan::MarkedForCache; } bool PassedConditionForMemoryCache() const { - return mMemoryCachingPlan == CachingPlan::PassedCondition; + return mMemoryCachingPlan == CachingPlan::PassedCondition || + mMemoryCachingPlan == CachingPlan::MarkedForCache; } bool PassedConditionForEitherCache() const { @@ -225,9 +229,43 @@ class ScriptLoadRequest : public nsISupports, mMemoryCachingPlan = CachingPlan::PassedCondition; } + bool IsMarkedForDiskCache() const { + return mDiskCachingPlan == CachingPlan::MarkedForCache; + } + + bool IsMarkedForMemoryCache() const { + return mMemoryCachingPlan == CachingPlan::MarkedForCache; + } + + bool IsMarkedForEitherCache() const { + return IsMarkedForDiskCache() || IsMarkedForMemoryCache(); + } + + protected: + void MarkForCache() { + MOZ_ASSERT(mDiskCachingPlan == CachingPlan::PassedCondition || + mMemoryCachingPlan == CachingPlan::PassedCondition); + + if (mDiskCachingPlan == CachingPlan::PassedCondition) { + mDiskCachingPlan = CachingPlan::MarkedForCache; + } + if (mMemoryCachingPlan == CachingPlan::PassedCondition) { + mMemoryCachingPlan = CachingPlan::MarkedForCache; + } + } + public: + void MarkScriptForCache(JSScript* aScript); + mozilla::CORSMode CORSMode() const { return mFetchOptions->mCORSMode; } + // Check the reference to the cache info channel, which is used by the disk + // cache. + bool HasDiskCacheReference() const { return !!mCacheInfo; } + + // Drop the reference to the cache info channel. + void DropDiskCacheReference(); + bool HasLoadContext() const { return mLoadContext; } bool HasScriptLoadContext() const; bool HasWorkerLoadContext() const; @@ -272,6 +310,10 @@ class ScriptLoadRequest : public nsISupports, // This fits the condition for the caching (e.g. file size, fetch count). PassedCondition, + + // This is marked for encoding, with setting sufficient input, + // e.g. mScriptForCache for script. + MarkedForCache, }; CachingPlan mDiskCachingPlan = CachingPlan::Uninitialized; CachingPlan mMemoryCachingPlan = CachingPlan::Uninitialized; @@ -323,6 +365,16 @@ class ScriptLoadRequest : public nsISupports, // would share the same loaded script. RefPtr<LoadedScript> mLoadedScript; + // Holds the top-level JSScript that corresponds to the current source, once + // it is parsed, and marked to be saved in the bytecode cache. + // + // NOTE: This field is not used for ModuleLoadRequest. + JS::Heap<JSScript*> mScriptForCache; + + // Holds the Cache information, which is used to register the bytecode + // on the cache entry, such that we can load it the next time. + nsCOMPtr<nsICacheInfoChannel> mCacheInfo; + // LoadContext for augmenting the load depending on the loading // context (DOM, Worker, etc.) RefPtr<LoadContextBase> mLoadContext; diff --git a/js/public/experimental/JSStencil.h b/js/public/experimental/JSStencil.h @@ -207,9 +207,6 @@ namespace JS { extern JS_PUBLIC_API TranscodeResult EncodeStencil(JSContext* cx, Stencil* stencil, TranscodeBuffer& buffer); -extern JS_PUBLIC_API TranscodeResult EncodeStencil(JS::FrontendContext* fc, - Stencil* stencil, - TranscodeBuffer& buffer); // Deserialize data and create a new Stencil. extern JS_PUBLIC_API TranscodeResult diff --git a/js/src/frontend/StencilXdr.cpp b/js/src/frontend/StencilXdr.cpp @@ -1486,15 +1486,11 @@ static JS::TranscodeResult EncodeStencilImpl( JS::TranscodeResult JS::EncodeStencil(JSContext* cx, JS::Stencil* stencil, JS::TranscodeBuffer& buffer) { AutoReportFrontendContext fc(cx); - return JS::EncodeStencil(&fc, stencil, buffer); -} -JS::TranscodeResult JS::EncodeStencil(FrontendContext* fc, JS::Stencil* stencil, - JS::TranscodeBuffer& buffer) { const CompilationStencil* initial; UniquePtr<CompilationStencil> merged; if (stencil->canLazilyParse()) { - merged.reset(stencil->getMerged(fc)); + merged.reset(stencil->getMerged(&fc)); if (!merged) { return TranscodeResult::Throw; } @@ -1503,7 +1499,7 @@ JS::TranscodeResult JS::EncodeStencil(FrontendContext* fc, JS::Stencil* stencil, initial = stencil->getInitial(); } - return EncodeStencilImpl(fc, initial, buffer); + return EncodeStencilImpl(&fc, initial, buffer); } JS::TranscodeResult js::EncodeStencil(JSContext* cx, @@ -1513,12 +1509,6 @@ JS::TranscodeResult js::EncodeStencil(JSContext* cx, return EncodeStencilImpl(&fc, stencil, buffer); } -JS::TranscodeResult js::EncodeStencil(FrontendContext* fc, - frontend::CompilationStencil* stencil, - JS::TranscodeBuffer& buffer) { - return EncodeStencilImpl(fc, stencil, buffer); -} - XDRResult XDRStencilDecoder::codeStencil( const JS::ReadOnlyDecodeOptions& options, frontend::CompilationStencil& stencil) { diff --git a/js/src/frontend/StencilXdr.h b/js/src/frontend/StencilXdr.h @@ -218,10 +218,6 @@ JS::TranscodeResult EncodeStencil(JSContext* cx, frontend::CompilationStencil* stencil, JS::TranscodeBuffer& buffer); -JS::TranscodeResult EncodeStencil(JS::FrontendContext* fc, - frontend::CompilationStencil* stencil, - JS::TranscodeBuffer& buffer); - JS::TranscodeResult DecodeStencil(JS::FrontendContext* fc, const JS::ReadOnlyDecodeOptions& options, const JS::TranscodeRange& range, diff --git a/js/xpconnect/loader/ScriptCacheActors.cpp b/js/xpconnect/loader/ScriptCacheActors.cpp @@ -34,16 +34,13 @@ void ScriptCacheChild::SendScriptsAndFinalize( ScriptPreloader::ScriptHash& scripts) { MOZ_ASSERT(mWantCacheData); - auto matcher = ScriptPreloader::Match<ScriptPreloader::ScriptStatus::Saved>(); + AutoSafeJSAPI jsapi; - JS::FrontendContext* fc = JS::NewFrontendContext(); - if (!fc) { - return; - } + auto matcher = ScriptPreloader::Match<ScriptPreloader::ScriptStatus::Saved>(); nsTArray<ScriptData> dataArray; for (auto& script : IterHash(scripts, matcher)) { - if (!script->mSize && !script->XDREncode(fc)) { + if (!script->mSize && !script->XDREncode(jsapi.cx())) { continue; } @@ -60,8 +57,6 @@ void ScriptCacheChild::SendScriptsAndFinalize( } } - JS::DestroyFrontendContext(fc); - Send__delete__(this, dataArray); } diff --git a/js/xpconnect/loader/ScriptPreloader.cpp b/js/xpconnect/loader/ScriptPreloader.cpp @@ -633,11 +633,8 @@ void ScriptPreloader::PrepareCacheWriteInternal() { return; } - JS::FrontendContext* fc = JS::NewFrontendContext(); - if (!fc) { - return; - } - + AutoSafeJSAPI jsapi; + JSAutoRealm ar(jsapi.cx(), xpc::PrivilegedJunkScope()); bool found = false; for (auto& script : IterHash(mScripts, Match<ScriptStatus::Saved>())) { // Don't write any scripts that are also in the child cache. They'll be @@ -657,13 +654,11 @@ void ScriptPreloader::PrepareCacheWriteInternal() { found = true; } - if (!script->mSize && !script->XDREncode(fc)) { + if (!script->mSize && !script->XDREncode(jsapi.cx())) { script.Remove(); } } - JS::DestroyFrontendContext(fc); - if (!found) { mSaveComplete = true; return; @@ -1276,20 +1271,19 @@ ScriptPreloader::CachedStencil::CachedStencil(ScriptPreloader& cache, mProcessTypes = {}; } -bool ScriptPreloader::CachedStencil::XDREncode(JS::FrontendContext* aFc) { +bool ScriptPreloader::CachedStencil::XDREncode(JSContext* cx) { auto cleanup = MakeScopeExit([&]() { MaybeDropStencil(); }); mXDRData.construct<JS::TranscodeBuffer>(); - JS::TranscodeResult code = JS::EncodeStencil(aFc, mStencil, Buffer()); - + JS::TranscodeResult code = JS::EncodeStencil(cx, mStencil, Buffer()); if (code == JS::TranscodeResult::Ok) { mXDRRange.emplace(Buffer().begin(), Buffer().length()); mSize = Range().length(); return true; } mXDRData.destroy(); - JS::ClearFrontendErrors(aFc); + JS_ClearPendingException(cx); return false; } diff --git a/js/xpconnect/loader/ScriptPreloader.h b/js/xpconnect/loader/ScriptPreloader.h @@ -31,10 +31,9 @@ #include "nsITimer.h" #include "js/CompileOptions.h" // JS::DecodeOptions, JS::ReadOnlyDecodeOptions -#include "js/experimental/CompileScript.h" // JS::FrontendContext -#include "js/experimental/JSStencil.h" // JS::Stencil -#include "js/GCAnnotations.h" // for JS_HAZ_NON_GC_POINTER -#include "js/RootingAPI.h" // for Handle, Heap +#include "js/experimental/JSStencil.h" // JS::Stencil +#include "js/GCAnnotations.h" // for JS_HAZ_NON_GC_POINTER +#include "js/RootingAPI.h" // for Handle, Heap #include "js/Transcoding.h" // for TranscodeBuffer, TranscodeRange, TranscodeSource #include "js/TypeDecls.h" // for HandleObject, HandleScript @@ -274,7 +273,7 @@ class ScriptPreloader : public nsIObserver, // Encodes this script into XDR data, and stores the result in mXDRData. // Returns true on success, false on failure. - bool XDREncode(JS::FrontendContext* cx); + bool XDREncode(JSContext* cx); // Encodes or decodes this script, in the storage format required by the // script cache file.