diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/workers/ScriptLoader.cpp | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | dom/workers/ScriptLoader.cpp | 1414 |
1 files changed, 1414 insertions, 0 deletions
diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp new file mode 100644 index 0000000000..e25e4b4e94 --- /dev/null +++ b/dom/workers/ScriptLoader.cpp @@ -0,0 +1,1414 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScriptLoader.h" + +#include <algorithm> +#include <type_traits> + +#include "nsIChannel.h" +#include "nsIContentPolicy.h" +#include "nsIContentSecurityPolicy.h" +#include "nsICookieJarSettings.h" +#include "nsIDocShell.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIIOService.h" +#include "nsIOService.h" +#include "nsIPrincipal.h" +#include "nsIProtocolHandler.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsIStreamListenerTee.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsIURI.h" +#include "nsIXPConnect.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/CompilationAndEvaluation.h" +#include "js/Exception.h" +#include "js/SourceText.h" +#include "nsError.h" +#include "nsComponentManagerUtils.h" +#include "nsContentPolicyUtils.h" +#include "nsContentUtils.h" +#include "nsDocShellCID.h" +#include "nsJSEnvironment.h" +#include "nsNetUtil.h" +#include "nsIPipe.h" +#include "nsIOutputStream.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "xpcpublic.h" + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/Assertions.h" +#include "mozilla/Encoding.h" +#include "mozilla/LoadContext.h" +#include "mozilla/Maybe.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/dom/ClientChannelHelper.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/nsCSPService.h" +#include "mozilla/dom/nsCSPUtils.h" +#include "mozilla/dom/PerformanceStorage.h" +#include "mozilla/dom/Response.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SerializedStackHolder.h" +#include "mozilla/dom/workerinternals/CacheLoadHandler.h" +#include "mozilla/dom/workerinternals/NetworkLoadHandler.h" +#include "mozilla/dom/workerinternals/ScriptResponseHeaderProcessor.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/UniquePtr.h" +#include "WorkerRunnable.h" +#include "WorkerScope.h" + +#define MAX_CONCURRENT_SCRIPTS 1000 + +using JS::loader::ScriptKind; +using JS::loader::ScriptLoadRequest; +using mozilla::ipc::PrincipalInfo; + +namespace mozilla::dom::workerinternals { +namespace { + +nsresult ConstructURI(const nsAString& aScriptURL, nsIURI* baseURI, + const mozilla::Encoding* aDocumentEncoding, + nsIURI** aResult) { + nsresult rv; + // Only top level workers' main script use the document charset for the + // script uri encoding. Otherwise, default encoding (UTF-8) is applied. + if (aDocumentEncoding) { + nsAutoCString charset; + aDocumentEncoding->Name(charset); + rv = NS_NewURI(aResult, aScriptURL, charset.get(), baseURI); + } else { + rv = NS_NewURI(aResult, aScriptURL, nullptr, baseURI); + } + + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + return NS_OK; +} + +nsresult ChannelFromScriptURL( + nsIPrincipal* principal, Document* parentDoc, WorkerPrivate* aWorkerPrivate, + nsILoadGroup* loadGroup, nsIIOService* ios, + nsIScriptSecurityManager* secMan, nsIURI* aScriptURL, + const Maybe<ClientInfo>& aClientInfo, + const Maybe<ServiceWorkerDescriptor>& aController, bool aIsMainScript, + WorkerScriptType aWorkerScriptType, + nsContentPolicyType aMainScriptContentPolicyType, nsLoadFlags aLoadFlags, + nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo, + nsIChannel** aChannel) { + AssertIsOnMainThread(); + + nsresult rv; + nsCOMPtr<nsIURI> uri = aScriptURL; + + // If we have the document, use it. Unfortunately, for dedicated workers + // 'parentDoc' ends up being the parent document, which is not the document + // that we want to use. So make sure to avoid using 'parentDoc' in that + // situation. + if (parentDoc && parentDoc->NodePrincipal() != principal) { + parentDoc = nullptr; + } + + uint32_t secFlags = + aIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED + : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT; + + bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal( + principal, uri, true /* aInheritForAboutBlank */, + false /* aForceInherit */); + + bool isData = uri->SchemeIs("data"); + if (inheritAttrs && !isData) { + secFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + + if (aWorkerScriptType == DebuggerScript) { + // A DebuggerScript needs to be a local resource like chrome: or resource: + bool isUIResource = false; + rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, + &isUIResource); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isUIResource) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + secFlags |= nsILoadInfo::SEC_ALLOW_CHROME; + } + + // Note: this is for backwards compatibility and goes against spec. + // We should find a better solution. + if (aIsMainScript && isData) { + secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL; + } + + nsContentPolicyType contentPolicyType = + aIsMainScript ? aMainScriptContentPolicyType + : nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS; + + // The main service worker script should never be loaded over the network + // in this path. It should always be offlined by ServiceWorkerScriptCache. + // We assert here since this error should also be caught by the runtime + // check in CacheLoadHandler. + // + // Note, if we ever allow service worker scripts to be loaded from network + // here we need to configure the channel properly. For example, it must + // not allow redirects. + MOZ_DIAGNOSTIC_ASSERT(contentPolicyType != + nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER); + + nsCOMPtr<nsIChannel> channel; + if (parentDoc) { + rv = NS_NewChannel(getter_AddRefs(channel), uri, parentDoc, secFlags, + contentPolicyType, + nullptr, // aPerformanceStorage + loadGroup, + nullptr, // aCallbacks + aLoadFlags, ios); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); + } else { + // We must have a loadGroup with a load context for the principal to + // traverse the channel correctly. + MOZ_ASSERT(loadGroup); + MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal)); + + RefPtr<PerformanceStorage> performanceStorage; + nsCOMPtr<nsICSPEventListener> cspEventListener; + if (aWorkerPrivate && !aIsMainScript) { + performanceStorage = aWorkerPrivate->GetPerformanceStorage(); + cspEventListener = aWorkerPrivate->CSPEventListener(); + } + + if (aClientInfo.isSome()) { + rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, + aClientInfo.ref(), aController, secFlags, + contentPolicyType, aCookieJarSettings, + performanceStorage, loadGroup, nullptr, // aCallbacks + aLoadFlags, ios); + } else { + rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, secFlags, + contentPolicyType, aCookieJarSettings, + performanceStorage, loadGroup, nullptr, // aCallbacks + aLoadFlags, ios); + } + + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); + + if (cspEventListener) { + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + rv = loadInfo->SetCspEventListener(cspEventListener); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + if (aReferrerInfo) { + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel); + if (httpChannel) { + rv = httpChannel->SetReferrerInfo(aReferrerInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + + channel.forget(aChannel); + return rv; +} + +void LoadAllScripts(WorkerPrivate* aWorkerPrivate, + UniquePtr<SerializedStackHolder> aOriginStack, + const nsTArray<nsString>& aScriptURLs, bool aIsMainScript, + WorkerScriptType aWorkerScriptType, ErrorResult& aRv, + const mozilla::Encoding* aDocumentEncoding = nullptr) { + aWorkerPrivate->AssertIsOnWorkerThread(); + NS_ASSERTION(!aScriptURLs.IsEmpty(), "Bad arguments!"); + + AutoSyncLoopHolder syncLoop(aWorkerPrivate, Canceling); + nsCOMPtr<nsISerialEventTarget> syncLoopTarget = + syncLoop.GetSerialEventTarget(); + if (!syncLoopTarget) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + RefPtr<loader::WorkerScriptLoader> loader = + new loader::WorkerScriptLoader(aWorkerPrivate, std::move(aOriginStack), + syncLoopTarget, aWorkerScriptType, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + loader->CreateScriptRequests(aScriptURLs, aDocumentEncoding, aIsMainScript); + + if (loader->DispatchLoadScripts()) { + syncLoop.Run(); + } +} + +class ChannelGetterRunnable final : public WorkerMainThreadRunnable { + const nsAString& mScriptURL; + const ClientInfo mClientInfo; + WorkerLoadInfo& mLoadInfo; + nsresult mResult; + + public: + ChannelGetterRunnable(WorkerPrivate* aParentWorker, + const nsAString& aScriptURL, WorkerLoadInfo& aLoadInfo) + : WorkerMainThreadRunnable(aParentWorker, + "ScriptLoader :: ChannelGetter"_ns), + mScriptURL(aScriptURL) + // ClientInfo should always be present since this should not be called + // if parent's status is greater than Running. + , + mClientInfo(aParentWorker->GlobalScope()->GetClientInfo().ref()), + mLoadInfo(aLoadInfo), + mResult(NS_ERROR_FAILURE) { + MOZ_ASSERT(aParentWorker); + aParentWorker->AssertIsOnWorkerThread(); + } + + virtual bool MainThreadRun() override { + AssertIsOnMainThread(); + + // Initialize the WorkerLoadInfo principal to our triggering principal + // before doing anything else. Normally we do this in the WorkerPrivate + // Constructor, but we can't do so off the main thread when creating + // a nested worker. So do it here instead. + mLoadInfo.mLoadingPrincipal = mWorkerPrivate->GetPrincipal(); + MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mLoadingPrincipal); + + mLoadInfo.mPrincipal = mLoadInfo.mLoadingPrincipal; + + // Figure out our base URI. + nsCOMPtr<nsIURI> baseURI = mWorkerPrivate->GetBaseURI(); + MOZ_ASSERT(baseURI); + + // May be null. + nsCOMPtr<Document> parentDoc = mWorkerPrivate->GetDocument(); + + mLoadInfo.mLoadGroup = mWorkerPrivate->GetLoadGroup(); + mLoadInfo.mCookieJarSettings = mWorkerPrivate->CookieJarSettings(); + + // Nested workers use default uri encoding. + nsCOMPtr<nsIURI> url; + mResult = ConstructURI(mScriptURL, baseURI, nullptr, getter_AddRefs(url)); + NS_ENSURE_SUCCESS(mResult, true); + + Maybe<ClientInfo> clientInfo; + clientInfo.emplace(mClientInfo); + + nsCOMPtr<nsIChannel> channel; + nsCOMPtr<nsIReferrerInfo> referrerInfo = + ReferrerInfo::CreateForFetch(mLoadInfo.mLoadingPrincipal, nullptr); + mLoadInfo.mReferrerInfo = + static_cast<ReferrerInfo*>(referrerInfo.get()) + ->CloneWithNewPolicy(mWorkerPrivate->GetReferrerPolicy()); + + mResult = workerinternals::ChannelFromScriptURLMainThread( + mLoadInfo.mLoadingPrincipal, parentDoc, mLoadInfo.mLoadGroup, url, + clientInfo, + // Nested workers are always dedicated. + nsIContentPolicy::TYPE_INTERNAL_WORKER, mLoadInfo.mCookieJarSettings, + mLoadInfo.mReferrerInfo, getter_AddRefs(channel)); + NS_ENSURE_SUCCESS(mResult, true); + + mResult = mLoadInfo.SetPrincipalsAndCSPFromChannel(channel); + NS_ENSURE_SUCCESS(mResult, true); + + mLoadInfo.mChannel = std::move(channel); + return true; + } + + nsresult GetResult() const { return mResult; } + + private: + virtual ~ChannelGetterRunnable() = default; +}; + +} // anonymous namespace + +namespace loader { + +class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable { + RefPtr<WorkerScriptLoader> mScriptLoader; + const Span<RefPtr<ThreadSafeRequestHandle>> mLoadedRequests; + + public: + ScriptExecutorRunnable(WorkerScriptLoader* aScriptLoader, + WorkerPrivate* aWorkerPrivate, + nsISerialEventTarget* aSyncLoopTarget, + Span<RefPtr<ThreadSafeRequestHandle>> aLoadedRequests); + + private: + ~ScriptExecutorRunnable() = default; + + virtual bool IsDebuggerRunnable() const override; + + virtual bool PreRun(WorkerPrivate* aWorkerPrivate) override; + + virtual bool WorkerRun(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) override; + + nsresult Cancel() override; +}; + +template <typename Unit> +static bool EvaluateSourceBuffer(JSContext* aCx, + const JS::CompileOptions& aOptions, + JS::SourceText<Unit>& aSourceBuffer) { + static_assert(std::is_same<Unit, char16_t>::value || + std::is_same<Unit, Utf8Unit>::value, + "inferred units must be UTF-8 or UTF-16"); + + JS::Rooted<JS::Value> unused(aCx); + return Evaluate(aCx, aOptions, aSourceBuffer, &unused); +} + +WorkerScriptLoader::WorkerScriptLoader( + WorkerPrivate* aWorkerPrivate, + UniquePtr<SerializedStackHolder> aOriginStack, + nsISerialEventTarget* aSyncLoopTarget, WorkerScriptType aWorkerScriptType, + ErrorResult& aRv) + : mOriginStack(std::move(aOriginStack)), + mSyncLoopTarget(aSyncLoopTarget), + mWorkerScriptType(aWorkerScriptType), + mRv(aRv), + mCleanedUp(false), + mCleanUpLock("cleanUpLock") { + aWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(aSyncLoopTarget); + + RefPtr<WorkerScriptLoader> self = this; + + RefPtr<StrongWorkerRef> workerRef = + StrongWorkerRef::Create(aWorkerPrivate, "ScriptLoader", [self]() {}); + + if (workerRef) { + mWorkerRef = new ThreadSafeWorkerRef(workerRef); + } else { + mRv.Throw(NS_ERROR_FAILURE); + return; + } + + nsIGlobalObject* global = GetGlobal(); + + mController = global->GetController(); +} + +void WorkerScriptLoader::CreateScriptRequests( + const nsTArray<nsString>& aScriptURLs, + const mozilla::Encoding* aDocumentEncoding, bool aIsMainScript) { + for (const nsString& scriptURL : aScriptURLs) { + RefPtr<ScriptLoadRequest> request = + CreateScriptLoadRequest(scriptURL, aDocumentEncoding, aIsMainScript); + mLoadingRequests.AppendElement(request); + } +} + +nsTArray<RefPtr<ThreadSafeRequestHandle>> WorkerScriptLoader::GetLoadingList() { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + nsTArray<RefPtr<ThreadSafeRequestHandle>> list; + for (ScriptLoadRequest* req = mLoadingRequests.getFirst(); req; + req = req->getNext()) { + RefPtr<ThreadSafeRequestHandle> handle = + new ThreadSafeRequestHandle(req, mSyncLoopTarget.get()); + list.AppendElement(handle.forget()); + } + return list; +} + +already_AddRefed<ScriptLoadRequest> WorkerScriptLoader::CreateScriptLoadRequest( + const nsString& aScriptURL, const mozilla::Encoding* aDocumentEncoding, + bool aIsMainScript) { + WorkerLoadContext::Kind kind = + WorkerLoadContext::GetKind(aIsMainScript, IsDebuggerScript()); + + Maybe<ClientInfo> clientInfo = GetGlobal()->GetClientInfo(); + + RefPtr<WorkerLoadContext> loadContext = + new WorkerLoadContext(kind, clientInfo); + + // Create ScriptLoadRequests for this WorkerScriptLoader + ReferrerPolicy referrerPolicy = mWorkerRef->Private()->GetReferrerPolicy(); + + // Only top level workers' main script use the document charset for the + // script uri encoding. Otherwise, default encoding (UTF-8) is applied. + MOZ_ASSERT_IF(bool(aDocumentEncoding), + aIsMainScript && !mWorkerRef->Private()->GetParent()); + nsCOMPtr<nsIURI> baseURI = aIsMainScript ? GetInitialBaseURI() : GetBaseURI(); + nsCOMPtr<nsIURI> uri; + nsresult rv = + ConstructURI(aScriptURL, baseURI, aDocumentEncoding, getter_AddRefs(uri)); + // If we failed to construct the URI, handle it in the LoadContext so it is + // thrown in the right order. + if (NS_WARN_IF(NS_FAILED(rv))) { + loadContext->mLoadResult = rv; + } + + RefPtr<ScriptFetchOptions> fetchOptions = + new ScriptFetchOptions(CORSMode::CORS_NONE, referrerPolicy, nullptr); + + RefPtr<ScriptLoadRequest> request = + new ScriptLoadRequest(ScriptKind::eClassic, uri, fetchOptions, + SRIMetadata(), nullptr, /* = aReferrer */ + loadContext); + + // Set the mURL, it will be used for error handling and debugging. + request->mURL = NS_ConvertUTF16toUTF8(aScriptURL); + + return request.forget(); +} + +bool WorkerScriptLoader::DispatchLoadScript(ScriptLoadRequest* aRequest) { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + nsTArray<RefPtr<ThreadSafeRequestHandle>> scriptLoadList; + RefPtr<ThreadSafeRequestHandle> handle = + new ThreadSafeRequestHandle(aRequest, mSyncLoopTarget.get()); + scriptLoadList.AppendElement(handle.forget()); + + RefPtr<ScriptLoaderRunnable> runnable = + new ScriptLoaderRunnable(this, std::move(scriptLoadList)); + + RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create( + mWorkerRef->Private(), "ScriptLoader", [runnable]() { + NS_DispatchToMainThread(NewRunnableMethod( + "ScriptLoaderRunnable::CancelMainThreadWithBindingAborted", + runnable, + &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted)); + }); + + if (NS_FAILED(NS_DispatchToMainThread(runnable))) { + NS_ERROR("Failed to dispatch!"); + mRv.Throw(NS_ERROR_FAILURE); + return false; + } + return true; +} + +bool WorkerScriptLoader::DispatchLoadScripts() { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + nsTArray<RefPtr<ThreadSafeRequestHandle>> scriptLoadList = GetLoadingList(); + + RefPtr<ScriptLoaderRunnable> runnable = + new ScriptLoaderRunnable(this, std::move(scriptLoadList)); + + RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create( + mWorkerRef->Private(), "ScriptLoader", [runnable]() { + NS_DispatchToMainThread(NewRunnableMethod( + "ScriptLoaderRunnable::CancelMainThreadWithBindingAborted", + runnable, + &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted)); + }); + + if (NS_FAILED(NS_DispatchToMainThread(runnable))) { + NS_ERROR("Failed to dispatch!"); + mRv.Throw(NS_ERROR_FAILURE); + return false; + } + return true; +} + +nsIURI* WorkerScriptLoader::GetInitialBaseURI() { + MOZ_ASSERT(mWorkerRef->Private()); + nsIURI* baseURI; + WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent(); + if (parentWorker) { + baseURI = parentWorker->GetBaseURI(); + } else { + // May be null. + baseURI = mWorkerRef->Private()->GetBaseURI(); + } + + return baseURI; +} + +nsIURI* WorkerScriptLoader::GetBaseURI() const { + MOZ_ASSERT(mWorkerRef); + nsIURI* baseURI; + baseURI = mWorkerRef->Private()->GetBaseURI(); + NS_ASSERTION(baseURI, "Should have been set already!"); + + return baseURI; +} + +nsIGlobalObject* WorkerScriptLoader::GetGlobal() { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + return mWorkerScriptType == WorkerScript + ? static_cast<nsIGlobalObject*>( + mWorkerRef->Private()->GlobalScope()) + : mWorkerRef->Private()->DebuggerGlobalScope(); +} + +void WorkerScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + aRequest->SetReady(); + + // If the request is not in a list, we are in an illegal state. + MOZ_RELEASE_ASSERT(aRequest->isInList()); + + while (!mLoadingRequests.isEmpty()) { + ScriptLoadRequest* request = mLoadingRequests.getFirst(); + // We need to move requests in post order. If prior requests have not + // completed, delay execution. + if (!request->IsReadyToRun()) { + break; + } + + RefPtr<ScriptLoadRequest> req = mLoadingRequests.Steal(request); + mLoadedRequests.AppendElement(req); + } +} + +bool WorkerScriptLoader::StoreCSP() { + // We must be on the same worker as we started on. + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + if (!mWorkerRef->Private()->GetJSContext()) { + return false; + } + + MOZ_ASSERT(!mRv.Failed()); + + // Move the CSP from the workerLoadInfo in the corresponding Client + // where the CSP code expects it! + mWorkerRef->Private()->StoreCSPOnClient(); + return true; +} + +bool WorkerScriptLoader::ProcessPendingRequests(JSContext* aCx) { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + // Don't run if something else has already failed. + if (mExecutionAborted) { + mLoadedRequests.CancelRequestsAndClear(); + TryShutdown(); + return true; + } + + // If nothing else has failed, our ErrorResult better not be a failure + // either. + MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?"); + + // Slightly icky action at a distance, but there's no better place to stash + // this value, really. + JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); + MOZ_ASSERT(global); + + while (!mLoadedRequests.isEmpty()) { + RefPtr<ScriptLoadRequest> req = mLoadedRequests.StealFirst(); + // We don't have a ProcessRequest method (like we do on the DOM), as there + // isn't much processing that we need to do per request that isn't related + // to evaluation (the processsing done for the DOM is handled in + // DataRecievedFrom{Cache,Network} for workers. + // So, this inner loop calls EvaluateScript directly. This will change + // once modules are introduced as we will have some extra work to do. + if (!EvaluateScript(aCx, req)) { + mExecutionAborted = true; + WorkerLoadContext* loadContext = req->GetWorkerLoadContext(); + mMutedErrorFlag = loadContext->mMutedErrorFlag.valueOr(true); + mLoadedRequests.CancelRequestsAndClear(); + break; + } + } + + TryShutdown(); + return true; +} + +nsresult WorkerScriptLoader::LoadScript( + ThreadSafeRequestHandle* aRequestHandle) { + AssertIsOnMainThread(); + + WorkerLoadContext* loadContext = aRequestHandle->GetContext(); + MOZ_ASSERT_IF(loadContext->IsTopLevel(), !IsDebuggerScript()); + + // The URL passed to us for loading was invalid, stop loading at this point. + if (loadContext->mLoadResult != NS_ERROR_NOT_INITIALIZED) { + return loadContext->mLoadResult; + } + + WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent(); + + // For JavaScript debugging, the devtools server must run on the same + // thread as the debuggee, indicating the worker uses content principal. + // However, in Bug 863246, web content will no longer be able to load + // resource:// URIs by default, so we need system principal to load + // debugger scripts. + nsIPrincipal* principal = (IsDebuggerScript()) + ? nsContentUtils::GetSystemPrincipal() + : mWorkerRef->Private()->GetPrincipal(); + + nsCOMPtr<nsILoadGroup> loadGroup = mWorkerRef->Private()->GetLoadGroup(); + MOZ_DIAGNOSTIC_ASSERT(principal); + + NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadGroup, principal), + NS_ERROR_FAILURE); + + // May be null. + nsCOMPtr<Document> parentDoc = mWorkerRef->Private()->GetDocument(); + + nsCOMPtr<nsIChannel> channel; + if (loadContext->IsTopLevel()) { + // May be null. + channel = mWorkerRef->Private()->ForgetWorkerChannel(); + } + + nsCOMPtr<nsIIOService> ios(do_GetIOService()); + + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(secMan, "This should never be null!"); + + nsresult& rv = loadContext->mLoadResult; + + nsLoadFlags loadFlags = mWorkerRef->Private()->GetLoadFlags(); + + // Get the top-level worker. + WorkerPrivate* topWorkerPrivate = mWorkerRef->Private(); + WorkerPrivate* parent = topWorkerPrivate->GetParent(); + while (parent) { + topWorkerPrivate = parent; + parent = topWorkerPrivate->GetParent(); + } + + // If the top-level worker is a dedicated worker and has a window, and the + // window has a docshell, the caching behavior of this worker should match + // that of that docshell. + if (topWorkerPrivate->IsDedicatedWorker()) { + nsCOMPtr<nsPIDOMWindowInner> window = topWorkerPrivate->GetWindow(); + if (window) { + nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); + if (docShell) { + nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + if (!channel) { + nsCOMPtr<nsIReferrerInfo> referrerInfo = + ReferrerInfo::CreateForFetch(principal, nullptr); + if (parentWorker && !loadContext->IsTopLevel()) { + referrerInfo = + static_cast<ReferrerInfo*>(referrerInfo.get()) + ->CloneWithNewPolicy(parentWorker->GetReferrerPolicy()); + } + + rv = ChannelFromScriptURL( + principal, parentDoc, mWorkerRef->Private(), loadGroup, ios, secMan, + aRequestHandle->GetRequest()->mURI, loadContext->mClientInfo, + mController, loadContext->IsTopLevel(), mWorkerScriptType, + mWorkerRef->Private()->ContentPolicyType(), loadFlags, + mWorkerRef->Private()->CookieJarSettings(), referrerInfo, + getter_AddRefs(channel)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // Associate any originating stack with the channel. + if (!mOriginStackJSON.IsEmpty()) { + NotifyNetworkMonitorAlternateStack(channel, mOriginStackJSON); + } + + // We need to know which index we're on in OnStreamComplete so we know + // where to put the result. + RefPtr<NetworkLoadHandler> listener = + new NetworkLoadHandler(this, aRequestHandle); + + RefPtr<ScriptResponseHeaderProcessor> headerProcessor = nullptr; + + // For each debugger script, a non-debugger script load of the same script + // should have occured prior that processed the headers. + if (!IsDebuggerScript()) { + headerProcessor = MakeRefPtr<ScriptResponseHeaderProcessor>( + mWorkerRef->Private(), loadContext->IsTopLevel()); + } + + nsCOMPtr<nsIStreamLoader> loader; + rv = NS_NewStreamLoader(getter_AddRefs(loader), listener, headerProcessor); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (loadContext->IsTopLevel()) { + MOZ_DIAGNOSTIC_ASSERT(loadContext->mClientInfo.isSome()); + + // In order to get the correct foreign partitioned prinicpal, we need to + // set the `IsThirdPartyContextToTopWindow` to the channel's loadInfo. + // This flag reflects the fact that if the worker is created under a + // third-party context. + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + loadInfo->SetIsThirdPartyContextToTopWindow( + mWorkerRef->Private()->IsThirdPartyContextToTopWindow()); + + Maybe<ClientInfo> clientInfo; + clientInfo.emplace(loadContext->mClientInfo.ref()); + rv = AddClientChannelHelper(channel, std::move(clientInfo), + Maybe<ClientInfo>(), + mWorkerRef->Private()->HybridEventTarget()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) { + nsILoadInfo::CrossOriginEmbedderPolicy respectedCOEP = + mWorkerRef->Private()->GetEmbedderPolicy(); + if (mWorkerRef->Private()->IsDedicatedWorker() && + respectedCOEP == nsILoadInfo::EMBEDDER_POLICY_NULL) { + respectedCOEP = mWorkerRef->Private()->GetOwnerEmbedderPolicy(); + } + + nsCOMPtr<nsILoadInfo> channelLoadInfo = channel->LoadInfo(); + channelLoadInfo->SetLoadingEmbedderPolicy(respectedCOEP); + } + + if (loadContext->mCacheStatus != WorkerLoadContext::ToBeCached) { + rv = channel->AsyncOpen(loader); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + nsCOMPtr<nsIOutputStream> writer; + + // In case we return early. + loadContext->mCacheStatus = WorkerLoadContext::Cancel; + + NS_NewPipe(getter_AddRefs(loadContext->mCacheReadStream), + getter_AddRefs(writer), 0, + UINT32_MAX, // unlimited size to avoid writer WOULD_BLOCK case + true, false); // non-blocking reader, blocking writer + + nsCOMPtr<nsIStreamListenerTee> tee = + do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID); + rv = tee->Init(loader, writer, listener); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsresult rv = channel->AsyncOpen(tee); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + loadContext->mChannel.swap(channel); + + return NS_OK; +} + +nsresult WorkerScriptLoader::FillCompileOptionsForRequest( + JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions, + JS::MutableHandle<JSScript*> aIntroductionScript) { + // The full URL shouldn't be exposed to the debugger. See Bug 1634872 + aOptions->setFileAndLine(aRequest->mURL.get(), 1); + aOptions->setNoScriptRval(true); + + aOptions->setMutedErrors( + aRequest->GetWorkerLoadContext()->mMutedErrorFlag.value()); + + if (aRequest->mSourceMapURL) { + aOptions->setSourceMapURL(aRequest->mSourceMapURL->get()); + } + + return NS_OK; +} + +bool WorkerScriptLoader::EvaluateScript(JSContext* aCx, + ScriptLoadRequest* aRequest) { + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + WorkerLoadContext* loadContext = aRequest->GetWorkerLoadContext(); + + NS_ASSERTION(!loadContext->mChannel, "Should no longer have a channel!"); + NS_ASSERTION(aRequest->IsReadyToRun(), "Should be scheduled!"); + + MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?"); + mRv.MightThrowJSException(); + if (NS_FAILED(loadContext->mLoadResult)) { + ReportErrorToConsole(aRequest, loadContext->mLoadResult); + return false; + } + + // If this is a top level script that succeeded, then mark the + // Client execution ready and possible controlled by a service worker. + if (loadContext->IsTopLevel()) { + if (mController.isSome()) { + MOZ_ASSERT(mWorkerScriptType == WorkerScript, + "Debugger clients can't be controlled."); + mWorkerRef->Private()->GlobalScope()->Control(mController.ref()); + } + mWorkerRef->Private()->ExecutionReady(); + } + + JS::CompileOptions options(aCx); + // The introduction script is used by the DOM script loader as a way + // to fill the Debugger Metadata for the JS Execution context. We don't use + // the JS Execution context as we are not making use of async compilation + // (delegation to another worker to produce bytecode or compile a string to a + // JSScript), so it is not used in this context. + JS::Rooted<JSScript*> unusedIntroductionScript(aCx); + nsresult rv = FillCompileOptionsForRequest(aCx, aRequest, &options, + &unusedIntroductionScript); + + MOZ_ASSERT(NS_SUCCEEDED(rv), "Filling compile options should not fail"); + + // Our ErrorResult still shouldn't be a failure. + MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?"); + + // Get the source text. + ScriptLoadRequest::MaybeSourceText maybeSource; + rv = aRequest->GetScriptSource(aCx, &maybeSource); + if (NS_FAILED(rv)) { + mRv.StealExceptionFromJSContext(aCx); + return false; + } + + bool successfullyEvaluated = + aRequest->IsUTF8Text() + ? EvaluateSourceBuffer(aCx, options, + maybeSource.ref<JS::SourceText<Utf8Unit>>()) + : EvaluateSourceBuffer(aCx, options, + maybeSource.ref<JS::SourceText<char16_t>>()); + + if (!successfullyEvaluated) { + mRv.StealExceptionFromJSContext(aCx); + return false; + } + // steal the loadContext so that the cycle is broken and cycle collector can + // collect the scriptLoadRequest. + return true; +} + +void WorkerScriptLoader::TryShutdown() { + if (AllScriptsExecuted()) { + ShutdownScriptLoader(!mExecutionAborted, mMutedErrorFlag); + } +} + +void WorkerScriptLoader::ShutdownScriptLoader(bool aResult, bool aMutedError) { + MOZ_ASSERT(AllScriptsExecuted()); + mWorkerRef->Private()->AssertIsOnWorkerThread(); + + if (!aResult) { + // At this point there are two possibilities: + // + // 1) mRv.Failed(). In that case we just want to leave it + // as-is, except if it has a JS exception and we need to mute JS + // exceptions. In that case, we log the exception without firing any + // events and then replace it on the ErrorResult with a NetworkError, + // per spec. + // + // 2) mRv succeeded. As far as I can tell, this can only + // happen when loading the main worker script and + // GetOrCreateGlobalScope() fails or if ScriptExecutorRunnable::Cancel + // got called. Does it matter what we throw in this case? I'm not + // sure... + if (mRv.Failed()) { + if (aMutedError && mRv.IsJSException()) { + LogExceptionToConsole(mWorkerRef->Private()->GetJSContext(), + mWorkerRef->Private()); + mRv.Throw(NS_ERROR_DOM_NETWORK_ERR); + } + } else { + mRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + } + } + + // Lock, shutdown, and cleanup state. After this the Loader is closed. + { + MutexAutoLock lock(CleanUpLock()); + + if (CleanedUp()) { + return; + } + + mWorkerRef->Private()->AssertIsOnWorkerThread(); + mWorkerRef->Private()->StopSyncLoop(mSyncLoopTarget, + aResult ? NS_OK : NS_ERROR_FAILURE); + + // Signal cleanup + mCleanedUp = true; + + // Allow worker shutdown. + mWorkerRef = nullptr; + } +} + +void WorkerScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest, + nsresult aResult) const { + nsAutoString url = NS_ConvertUTF8toUTF16(aRequest->mURL); + workerinternals::ReportLoadError(mRv, aResult, url); +} + +void WorkerScriptLoader::LogExceptionToConsole(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) { + aWorkerPrivate->AssertIsOnWorkerThread(); + + MOZ_ASSERT(mRv.IsJSException()); + + JS::Rooted<JS::Value> exn(aCx); + if (!ToJSValue(aCx, std::move(mRv), &exn)) { + return; + } + + // Now the exception state should all be in exn. + MOZ_ASSERT(!JS_IsExceptionPending(aCx)); + MOZ_ASSERT(!mRv.Failed()); + + JS::ExceptionStack exnStack(aCx, exn, nullptr); + JS::ErrorReportBuilder report(aCx); + if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) { + JS_ClearPendingException(aCx); + return; + } + + RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport(); + xpcReport->Init(report.report(), report.toStringResult().c_str(), + aWorkerPrivate->IsChromeWorker(), aWorkerPrivate->WindowID()); + + RefPtr<AsyncErrorReporter> r = new AsyncErrorReporter(xpcReport); + NS_DispatchToMainThread(r); +} + +NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable, nsINamed) + +NS_IMPL_ISUPPORTS(WorkerScriptLoader, nsINamed) + +ScriptLoaderRunnable::ScriptLoaderRunnable( + WorkerScriptLoader* aScriptLoader, + nsTArray<RefPtr<ThreadSafeRequestHandle>> aLoadingRequests) + : mScriptLoader(aScriptLoader), + mWorkerRef(aScriptLoader->mWorkerRef), + mLoadingRequests(std::move(aLoadingRequests)), + mCancelMainThread(Nothing()) { + MOZ_ASSERT(aScriptLoader); +} + +nsresult ScriptLoaderRunnable::Run() { + AssertIsOnMainThread(); + + // Convert the origin stack to JSON (which must be done on the main + // thread) explicitly, so that we can use the stack to notify the net + // monitor about every script we load. We do this, rather than pass + // the stack directly to the netmonitor, in order to be able to use this + // for all subsequent scripts. + if (mScriptLoader->mOriginStack && + mScriptLoader->mOriginStackJSON.IsEmpty()) { + ConvertSerializedStackToJSON(std::move(mScriptLoader->mOriginStack), + mScriptLoader->mOriginStackJSON); + } + + if (!mWorkerRef->Private()->IsServiceWorker() || + mScriptLoader->IsDebuggerScript()) { + for (ThreadSafeRequestHandle* handle : mLoadingRequests) { + handle->mRunnable = this; + nsresult rv = mScriptLoader->LoadScript(handle); + if (NS_WARN_IF(NS_FAILED(rv))) { + LoadingFinished(handle, rv); + CancelMainThread(rv); + return rv; + } + } + + return NS_OK; + } + + MOZ_ASSERT(!mCacheCreator); + mCacheCreator = new CacheCreator(mWorkerRef->Private()); + + for (ThreadSafeRequestHandle* handle : mLoadingRequests) { + handle->mRunnable = this; + WorkerLoadContext* loadContext = handle->GetContext(); + mCacheCreator->AddLoader(MakeNotNull<RefPtr<CacheLoadHandler>>( + mWorkerRef, handle, loadContext->IsTopLevel(), mScriptLoader)); + } + + // The worker may have a null principal on first load, but in that case its + // parent definitely will have one. + nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal(); + if (!principal) { + WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent(); + MOZ_ASSERT(parentWorker, "Must have a parent!"); + principal = parentWorker->GetPrincipal(); + } + + nsresult rv = mCacheCreator->Load(principal); + if (NS_WARN_IF(NS_FAILED(rv))) { + CancelMainThread(rv); + return rv; + } + + return NS_OK; +} + +nsresult ScriptLoaderRunnable::OnStreamComplete( + ThreadSafeRequestHandle* aRequestHandle, nsresult aStatus) { + AssertIsOnMainThread(); + + LoadingFinished(aRequestHandle, aStatus); + return NS_OK; +} + +void ScriptLoaderRunnable::LoadingFinished( + ThreadSafeRequestHandle* aRequestHandle, nsresult aRv) { + AssertIsOnMainThread(); + + WorkerLoadContext* loadContext = aRequestHandle->GetContext(); + + loadContext->mLoadResult = aRv; + MOZ_ASSERT(!loadContext->mLoadingFinished); + loadContext->mLoadingFinished = true; + + if (loadContext->IsTopLevel() && NS_SUCCEEDED(aRv)) { + MOZ_DIAGNOSTIC_ASSERT( + mWorkerRef->Private()->PrincipalURIMatchesScriptURL()); + } + + MaybeExecuteFinishedScripts(aRequestHandle); +} + +void ScriptLoaderRunnable::MaybeExecuteFinishedScripts( + ThreadSafeRequestHandle* aRequestHandle) { + AssertIsOnMainThread(); + + // We execute the last step if we don't have a pending operation with the + // cache and the loading is completed. + WorkerLoadContext* loadContext = aRequestHandle->GetContext(); + if (!loadContext->IsAwaitingPromise()) { + if (aRequestHandle->GetContext()->IsTopLevel()) { + mWorkerRef->Private()->WorkerScriptLoaded(); + } + DispatchProcessPendingRequests(); + } +} + +void ScriptLoaderRunnable::CancelMainThreadWithBindingAborted() { + AssertIsOnMainThread(); + CancelMainThread(NS_BINDING_ABORTED); +} + +void ScriptLoaderRunnable::CancelMainThread(nsresult aCancelResult) { + AssertIsOnMainThread(); + if (IsCancelled()) { + return; + } + + { + MutexAutoLock lock(mScriptLoader->CleanUpLock()); + + // Check if we have already cancelled, or if the worker has been killed + // before we cancel. + if (mScriptLoader->CleanedUp()) { + return; + } + + mCancelMainThread = Some(aCancelResult); + + for (ThreadSafeRequestHandle* handle : mLoadingRequests) { + if (handle->IsEmpty()) { + continue; + } + + bool callLoadingFinished = true; + + WorkerLoadContext* loadContext = handle->GetContext(); + if (!loadContext) { + continue; + } + + if (loadContext->IsAwaitingPromise()) { + MOZ_ASSERT(mWorkerRef->Private()->IsServiceWorker()); + loadContext->mCachePromise->MaybeReject(NS_BINDING_ABORTED); + loadContext->mCachePromise = nullptr; + callLoadingFinished = false; + } + if (loadContext->mChannel) { + if (NS_SUCCEEDED(loadContext->mChannel->Cancel(aCancelResult))) { + callLoadingFinished = false; + } else { + NS_WARNING("Failed to cancel channel!"); + } + } + if (callLoadingFinished && !loadContext->mLoadingFinished) { + LoadingFinished(handle, aCancelResult); + } + } + DispatchProcessPendingRequests(); + } +} + +void ScriptLoaderRunnable::DispatchProcessPendingRequests() { + AssertIsOnMainThread(); + + const auto begin = mLoadingRequests.begin(); + const auto end = mLoadingRequests.end(); + using Iterator = decltype(begin); + const auto maybeRangeToExecute = + [begin, end]() -> Maybe<std::pair<Iterator, Iterator>> { + // firstItToExecute is the first loadInfo where mExecutionScheduled is + // unset. + auto firstItToExecute = std::find_if( + begin, end, [](const RefPtr<ThreadSafeRequestHandle>& requestHandle) { + return !requestHandle->mExecutionScheduled; + }); + + if (firstItToExecute == end) { + return Nothing(); + } + + // firstItUnexecutable is the first loadInfo that is not yet finished. + // Update mExecutionScheduled on the ones we're about to schedule for + // execution. + const auto firstItUnexecutable = + std::find_if(firstItToExecute, end, + [](RefPtr<ThreadSafeRequestHandle>& requestHandle) { + MOZ_ASSERT(!requestHandle->IsEmpty()); + if (!requestHandle->Finished()) { + return true; + } + + // We can execute this one. + requestHandle->mExecutionScheduled = true; + + return false; + }); + + return firstItUnexecutable == firstItToExecute + ? Nothing() + : Some(std::pair(firstItToExecute, firstItUnexecutable)); + }(); + + // If there are no unexecutable load infos, we can unuse things before the + // execution of the scripts and the stopping of the sync loop. + if (maybeRangeToExecute) { + if (maybeRangeToExecute->second == end) { + mCacheCreator = nullptr; + } + + RefPtr<ScriptExecutorRunnable> runnable = new ScriptExecutorRunnable( + mScriptLoader, mWorkerRef->Private(), mScriptLoader->mSyncLoopTarget, + Span<RefPtr<ThreadSafeRequestHandle>>{maybeRangeToExecute->first, + maybeRangeToExecute->second}); + if (!runnable->Dispatch()) { + MOZ_ASSERT(false, "This should never fail!"); + } + } +} + +ScriptExecutorRunnable::ScriptExecutorRunnable( + WorkerScriptLoader* aScriptLoader, WorkerPrivate* aWorkerPrivate, + nsISerialEventTarget* aSyncLoopTarget, + Span<RefPtr<ThreadSafeRequestHandle>> aLoadedRequests) + : MainThreadWorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget), + mScriptLoader(aScriptLoader), + mLoadedRequests(aLoadedRequests) {} + +bool ScriptExecutorRunnable::IsDebuggerRunnable() const { + // ScriptExecutorRunnable is used to execute both worker and debugger scripts. + // In the latter case, the runnable needs to be dispatched to the debugger + // queue. + return mScriptLoader->IsDebuggerScript(); +} + +bool ScriptExecutorRunnable::PreRun(WorkerPrivate* aWorkerPrivate) { + aWorkerPrivate->AssertIsOnWorkerThread(); + + // We must be on the same worker as we started on. + MOZ_ASSERT( + mScriptLoader->mSyncLoopTarget == mSyncLoopTarget, + "Unexpected SyncLoopTarget. Check if the sync loop was closed early"); + + if (!mLoadedRequests.begin()->get()->GetContext()->IsTopLevel()) { + return true; + } + + return mScriptLoader->StoreCSP(); +} + +bool ScriptExecutorRunnable::WorkerRun(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) { + aWorkerPrivate->AssertIsOnWorkerThread(); + + // There is a possibility that we cleaned up while this task was waiting to + // run. If this has happened, return and exit. + { + MutexAutoLock lock(mScriptLoader->CleanUpLock()); + if (mScriptLoader->CleanedUp()) { + return true; + } + + // We must be on the same worker as we started on. + MOZ_ASSERT( + mScriptLoader->mSyncLoopTarget == mSyncLoopTarget, + "Unexpected SyncLoopTarget. Check if the sync loop was closed early"); + + for (const auto& requestHandle : mLoadedRequests) { + // The request must be valid. + MOZ_ASSERT(!requestHandle->IsEmpty()); + + // Release the request to the worker. From this point on, the Request + // Handle is empty. + RefPtr<ScriptLoadRequest> request = requestHandle->ReleaseRequest(); + + mScriptLoader->MaybeMoveToLoadedList(request); + } + } + return mScriptLoader->ProcessPendingRequests(aCx); +} + +nsresult ScriptExecutorRunnable::Cancel() { + // We need to check first if cancel is called twice + nsresult rv = MainThreadWorkerSyncRunnable::Cancel(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mScriptLoader->AllScriptsExecuted()) { + mScriptLoader->ShutdownScriptLoader(false, false); + } + return NS_OK; +} + +} /* namespace loader */ + +nsresult ChannelFromScriptURLMainThread( + nsIPrincipal* aPrincipal, Document* aParentDoc, nsILoadGroup* aLoadGroup, + nsIURI* aScriptURL, const Maybe<ClientInfo>& aClientInfo, + nsContentPolicyType aMainScriptContentPolicyType, + nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo, + nsIChannel** aChannel) { + AssertIsOnMainThread(); + + nsCOMPtr<nsIIOService> ios(do_GetIOService()); + + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(secMan, "This should never be null!"); + + return ChannelFromScriptURL( + aPrincipal, aParentDoc, nullptr, aLoadGroup, ios, secMan, aScriptURL, + aClientInfo, Maybe<ServiceWorkerDescriptor>(), true, WorkerScript, + aMainScriptContentPolicyType, nsIRequest::LOAD_NORMAL, aCookieJarSettings, + aReferrerInfo, aChannel); +} + +nsresult ChannelFromScriptURLWorkerThread(JSContext* aCx, + WorkerPrivate* aParent, + const nsAString& aScriptURL, + WorkerLoadInfo& aLoadInfo) { + aParent->AssertIsOnWorkerThread(); + + RefPtr<ChannelGetterRunnable> getter = + new ChannelGetterRunnable(aParent, aScriptURL, aLoadInfo); + + ErrorResult rv; + getter->Dispatch(Canceling, rv); + if (rv.Failed()) { + NS_ERROR("Failed to dispatch!"); + return rv.StealNSResult(); + } + + return getter->GetResult(); +} + +void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult, + const nsAString& aScriptURL) { + MOZ_ASSERT(!aRv.Failed()); + + nsPrintfCString err("Failed to load worker script at \"%s\"", + NS_ConvertUTF16toUTF8(aScriptURL).get()); + + switch (aLoadResult) { + case NS_ERROR_FILE_NOT_FOUND: + case NS_ERROR_NOT_AVAILABLE: + case NS_ERROR_CORRUPTED_CONTENT: + aRv.Throw(NS_ERROR_DOM_NETWORK_ERR); + break; + + case NS_ERROR_MALFORMED_URI: + case NS_ERROR_DOM_SYNTAX_ERR: + aRv.ThrowSyntaxError(err); + break; + + case NS_BINDING_ABORTED: + // Note: we used to pretend like we didn't set an exception for + // NS_BINDING_ABORTED, but then ShutdownScriptLoader did it anyway. The + // other callsite, in WorkerPrivate::Constructor, never passed in + // NS_BINDING_ABORTED. So just throw it directly here. Consumers will + // deal as needed. But note that we do NOT want to use one of the + // Throw*Error() methods on ErrorResult for this case, because that will + // make it impossible for consumers to realize that our error was + // NS_BINDING_ABORTED. + aRv.Throw(aLoadResult); + return; + + case NS_ERROR_DOM_BAD_URI: + // This is actually a security error. + case NS_ERROR_DOM_SECURITY_ERR: + aRv.ThrowSecurityError(err); + break; + + default: + // For lack of anything better, go ahead and throw a NetworkError here. + // We don't want to throw a JS exception, because for toplevel script + // loads that would get squelched. + aRv.ThrowNetworkError(nsPrintfCString( + "Failed to load worker script at %s (nsresult = 0x%" PRIx32 ")", + NS_ConvertUTF16toUTF8(aScriptURL).get(), + static_cast<uint32_t>(aLoadResult))); + return; + } +} + +void LoadMainScript(WorkerPrivate* aWorkerPrivate, + UniquePtr<SerializedStackHolder> aOriginStack, + const nsAString& aScriptURL, + WorkerScriptType aWorkerScriptType, ErrorResult& aRv, + const mozilla::Encoding* aDocumentEncoding) { + nsTArray<nsString> scriptURLs; + + scriptURLs.AppendElement(aScriptURL); + + LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), scriptURLs, true, + aWorkerScriptType, aRv, aDocumentEncoding); +} + +void Load(WorkerPrivate* aWorkerPrivate, + UniquePtr<SerializedStackHolder> aOriginStack, + const nsTArray<nsString>& aScriptURLs, + WorkerScriptType aWorkerScriptType, ErrorResult& aRv) { + const uint32_t urlCount = aScriptURLs.Length(); + + if (!urlCount) { + return; + } + + if (urlCount > MAX_CONCURRENT_SCRIPTS) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), aScriptURLs, false, + aWorkerScriptType, aRv); +} + +} // namespace mozilla::dom::workerinternals |