commit 713fa5219186425d0d209a8073bc0c61b277d14f parent dc1c78e9c37aba6ed05a4ec47c4bfcb16f57b51d Author: Serban Stanca <sstanca@mozilla.com> Date: Wed, 8 Oct 2025 15:44:14 +0300 Revert "Bug 1991370, Bug 1991380, Bug 1991358, Bug 1991081 - Part 4: Use FrontendContext-based EncodeStencil in ScriptPreloader. r=bthrall" for causing multiple bugs. This reverts commit b1f784aebbf90355fb6d522828211c7da836fbab. This reverts commit 7f4d0b5564cb2707c8c92a597fe6748d10c4340a. This reverts commit 7292c3390270b56248ee9dec327fe16e57fcc06c. This reverts commit cbe156f214fc871ee210f9beda6f5045eb1965b9. This reverts commit 3412fc553500750f8b02794298922a4227f2aa6e. This reverts commit 1b08161b6dc3e224ace166ed34e9f2d7e9af0a20. This reverts commit d0cf6f521076b372b46ed37c06dde272f4f96d1c. This reverts commit da56ddfb2f525cb01e75be984ca8c1cd9b1e2b09. This reverts commit 926bbd302aee0649d9d55b872169c218727f8669. This reverts commit 8c38c4c093d52db00d421c9889c66f8961502289. This reverts commit d6475781cdd53c622074bdfe0140ac677d4b1df8. This reverts commit 8044e8dae97b3dd854ec2ca8bbdc0f96269bc6d6. This reverts commit a3149ca9dd8da618fdfe398b5489c319a5d244b7. This reverts commit eca472223ff913c885f50c2114be652d8fe3520a. This reverts commit 6bc2703f1a028770723eeecc1f672f6144150806. Diffstat:
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; @@ -1622,6 +1622,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, @@ -1649,7 +1652,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.