diff options
Diffstat (limited to 'dom/workers/ScriptLoader.cpp')
-rw-r--r-- | dom/workers/ScriptLoader.cpp | 2290 |
1 files changed, 2290 insertions, 0 deletions
diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp new file mode 100644 index 0000000000..46545e737d --- /dev/null +++ b/dom/workers/ScriptLoader.cpp @@ -0,0 +1,2290 @@ +/* -*- 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 "nsIChannel.h" +#include "nsIContentPolicy.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIDocShell.h" +#include "nsIDOMDocument.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIInputStreamPump.h" +#include "nsIIOService.h" +#include "nsIProtocolHandler.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsIStreamLoader.h" +#include "nsIStreamListenerTee.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsIURI.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "nsError.h" +#include "nsContentPolicyUtils.h" +#include "nsContentUtils.h" +#include "nsDocShellCID.h" +#include "nsISupportsPrimitives.h" +#include "nsNetUtil.h" +#include "nsIPipe.h" +#include "nsIOutputStream.h" +#include "nsPrintfCString.h" +#include "nsScriptLoader.h" +#include "nsString.h" +#include "nsStreamUtils.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "xpcpublic.h" + +#include "mozilla/Assertions.h" +#include "mozilla/LoadContext.h" +#include "mozilla/Maybe.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/dom/CacheBinding.h" +#include "mozilla/dom/cache/CacheTypes.h" +#include "mozilla/dom/cache/Cache.h" +#include "mozilla/dom/cache/CacheStorage.h" +#include "mozilla/dom/ChannelInfo.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/InternalResponse.h" +#include "mozilla/dom/nsCSPService.h" +#include "mozilla/dom/nsCSPUtils.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseNativeHandler.h" +#include "mozilla/dom/Response.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SRILogHelper.h" +#include "mozilla/UniquePtr.h" +#include "Principal.h" +#include "WorkerHolder.h" +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" +#include "WorkerScope.h" + +#define MAX_CONCURRENT_SCRIPTS 1000 + +USING_WORKERS_NAMESPACE + +using namespace mozilla; +using namespace mozilla::dom; +using mozilla::dom::cache::Cache; +using mozilla::dom::cache::CacheStorage; +using mozilla::ipc::PrincipalInfo; + +namespace { + +nsIURI* +GetBaseURI(bool aIsMainScript, WorkerPrivate* aWorkerPrivate) +{ + MOZ_ASSERT(aWorkerPrivate); + nsIURI* baseURI; + WorkerPrivate* parentWorker = aWorkerPrivate->GetParent(); + if (aIsMainScript) { + if (parentWorker) { + baseURI = parentWorker->GetBaseURI(); + NS_ASSERTION(baseURI, "Should have been set already!"); + } + else { + // May be null. + baseURI = aWorkerPrivate->GetBaseURI(); + } + } + else { + baseURI = aWorkerPrivate->GetBaseURI(); + NS_ASSERTION(baseURI, "Should have been set already!"); + } + + return baseURI; +} + +nsresult +ChannelFromScriptURL(nsIPrincipal* principal, + nsIURI* baseURI, + nsIDocument* parentDoc, + nsILoadGroup* loadGroup, + nsIIOService* ios, + nsIScriptSecurityManager* secMan, + const nsAString& aScriptURL, + bool aIsMainScript, + WorkerScriptType aWorkerScriptType, + nsContentPolicyType aContentPolicyType, + nsLoadFlags aLoadFlags, + bool aDefaultURIEncoding, + nsIChannel** aChannel) +{ + AssertIsOnMainThread(); + + nsresult rv; + nsCOMPtr<nsIURI> uri; + + if (aDefaultURIEncoding) { + rv = NS_NewURI(getter_AddRefs(uri), aScriptURL, nullptr, baseURI); + } else { + rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), + aScriptURL, parentDoc, + baseURI); + } + + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + // 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; + } + + aLoadFlags |= nsIChannel::LOAD_CLASSIFY_URI; + uint32_t secFlags = aIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED + : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS; + + 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. + bool isData = false; + if (aIsMainScript && NS_SUCCEEDED(uri->SchemeIs("data", &isData)) && isData) { + secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL; + } + + nsCOMPtr<nsIChannel> channel; + // 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) { + rv = NS_NewChannel(getter_AddRefs(channel), + uri, + parentDoc, + secFlags, + aContentPolicyType, + loadGroup, + nullptr, // aCallbacks + aLoadFlags, + ios); + } 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)); + + rv = NS_NewChannel(getter_AddRefs(channel), + uri, + principal, + secFlags, + aContentPolicyType, + loadGroup, + nullptr, // aCallbacks + aLoadFlags, + ios); + } + + NS_ENSURE_SUCCESS(rv, rv); + + if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) { + rv = nsContentUtils::SetFetchReferrerURIWithPolicy(principal, parentDoc, + httpChannel, mozilla::net::RP_Default); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + channel.forget(aChannel); + return rv; +} + +struct ScriptLoadInfo +{ + ScriptLoadInfo() + : mScriptTextBuf(nullptr) + , mScriptTextLength(0) + , mLoadResult(NS_ERROR_NOT_INITIALIZED) + , mLoadingFinished(false) + , mExecutionScheduled(false) + , mExecutionResult(false) + , mCacheStatus(Uncached) + { } + + ~ScriptLoadInfo() + { + if (mScriptTextBuf) { + js_free(mScriptTextBuf); + } + } + + bool + ReadyToExecute() + { + return !mChannel && NS_SUCCEEDED(mLoadResult) && !mExecutionScheduled; + } + + nsString mURL; + + // This full URL string is populated only if this object is used in a + // ServiceWorker. + nsString mFullURL; + + // This promise is set only when the script is for a ServiceWorker but + // it's not in the cache yet. The promise is resolved when the full body is + // stored into the cache. mCachePromise will be set to nullptr after + // resolution. + RefPtr<Promise> mCachePromise; + + // The reader stream the cache entry should be filled from, for those cases + // when we're going to have an mCachePromise. + nsCOMPtr<nsIInputStream> mCacheReadStream; + + nsCOMPtr<nsIChannel> mChannel; + char16_t* mScriptTextBuf; + size_t mScriptTextLength; + + nsresult mLoadResult; + bool mLoadingFinished; + bool mExecutionScheduled; + bool mExecutionResult; + + enum CacheStatus { + // By default a normal script is just loaded from the network. But for + // ServiceWorkers, we have to check if the cache contains the script and + // load it from the cache. + Uncached, + + WritingToCache, + + ReadingFromCache, + + // This script has been loaded from the ServiceWorker cache. + Cached, + + // This script must be stored in the ServiceWorker cache. + ToBeCached, + + // Something went wrong or the worker went away. + Cancel + }; + + CacheStatus mCacheStatus; + + Maybe<bool> mMutedErrorFlag; + + bool Finished() const + { + return mLoadingFinished && !mCachePromise && !mChannel; + } +}; + +class ScriptLoaderRunnable; + +class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable +{ + ScriptLoaderRunnable& mScriptLoader; + bool mIsWorkerScript; + uint32_t mFirstIndex; + uint32_t mLastIndex; + +public: + ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader, + nsIEventTarget* aSyncLoopTarget, + bool aIsWorkerScript, + uint32_t aFirstIndex, + uint32_t aLastIndex); + +private: + ~ScriptExecutorRunnable() + { } + + virtual bool + IsDebuggerRunnable() const override; + + virtual bool + PreRun(WorkerPrivate* aWorkerPrivate) override; + + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override; + + virtual void + PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) + override; + + nsresult + Cancel() override; + + void + ShutdownScriptLoader(JSContext* aCx, + WorkerPrivate* aWorkerPrivate, + bool aResult, + bool aMutedError); + + void LogExceptionToConsole(JSContext* aCx, + WorkerPrivate* WorkerPrivate); +}; + +class CacheScriptLoader; + +class CacheCreator final : public PromiseNativeHandler +{ +public: + NS_DECL_ISUPPORTS + + explicit CacheCreator(WorkerPrivate* aWorkerPrivate) + : mCacheName(aWorkerPrivate->ServiceWorkerCacheName()) + , mOriginAttributes(aWorkerPrivate->GetOriginAttributes()) + { + MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); + MOZ_ASSERT(aWorkerPrivate->LoadScriptAsPartOfLoadingServiceWorkerScript()); + AssertIsOnMainThread(); + } + + void + AddLoader(CacheScriptLoader* aLoader) + { + AssertIsOnMainThread(); + MOZ_ASSERT(!mCacheStorage); + mLoaders.AppendElement(aLoader); + } + + virtual void + ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; + + virtual void + RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; + + // Try to load from cache with aPrincipal used for cache access. + nsresult + Load(nsIPrincipal* aPrincipal); + + Cache* + Cache_() const + { + AssertIsOnMainThread(); + MOZ_ASSERT(mCache); + return mCache; + } + + nsIGlobalObject* + Global() const + { + AssertIsOnMainThread(); + MOZ_ASSERT(mSandboxGlobalObject); + return mSandboxGlobalObject; + } + + void + DeleteCache(); + +private: + ~CacheCreator() + { + } + + nsresult + CreateCacheStorage(nsIPrincipal* aPrincipal); + + void + FailLoaders(nsresult aRv); + + RefPtr<Cache> mCache; + RefPtr<CacheStorage> mCacheStorage; + nsCOMPtr<nsIGlobalObject> mSandboxGlobalObject; + nsTArray<RefPtr<CacheScriptLoader>> mLoaders; + + nsString mCacheName; + PrincipalOriginAttributes mOriginAttributes; +}; + +NS_IMPL_ISUPPORTS0(CacheCreator) + +class CacheScriptLoader final : public PromiseNativeHandler + , public nsIStreamLoaderObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLOADEROBSERVER + + CacheScriptLoader(WorkerPrivate* aWorkerPrivate, ScriptLoadInfo& aLoadInfo, + uint32_t aIndex, bool aIsWorkerScript, + ScriptLoaderRunnable* aRunnable) + : mLoadInfo(aLoadInfo) + , mIndex(aIndex) + , mRunnable(aRunnable) + , mIsWorkerScript(aIsWorkerScript) + , mFailed(false) + { + MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); + mBaseURI = GetBaseURI(mIsWorkerScript, aWorkerPrivate); + AssertIsOnMainThread(); + } + + void + Fail(nsresult aRv); + + void + Load(Cache* aCache); + + virtual void + ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; + + virtual void + RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; + +private: + ~CacheScriptLoader() + { + AssertIsOnMainThread(); + } + + ScriptLoadInfo& mLoadInfo; + uint32_t mIndex; + RefPtr<ScriptLoaderRunnable> mRunnable; + bool mIsWorkerScript; + bool mFailed; + nsCOMPtr<nsIInputStreamPump> mPump; + nsCOMPtr<nsIURI> mBaseURI; + mozilla::dom::ChannelInfo mChannelInfo; + UniquePtr<PrincipalInfo> mPrincipalInfo; +}; + +NS_IMPL_ISUPPORTS(CacheScriptLoader, nsIStreamLoaderObserver) + +class CachePromiseHandler final : public PromiseNativeHandler +{ +public: + NS_DECL_ISUPPORTS + + CachePromiseHandler(ScriptLoaderRunnable* aRunnable, + ScriptLoadInfo& aLoadInfo, + uint32_t aIndex) + : mRunnable(aRunnable) + , mLoadInfo(aLoadInfo) + , mIndex(aIndex) + { + AssertIsOnMainThread(); + MOZ_ASSERT(mRunnable); + } + + virtual void + ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; + + virtual void + RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; + +private: + ~CachePromiseHandler() + { + AssertIsOnMainThread(); + } + + RefPtr<ScriptLoaderRunnable> mRunnable; + ScriptLoadInfo& mLoadInfo; + uint32_t mIndex; +}; + +NS_IMPL_ISUPPORTS0(CachePromiseHandler) + +class LoaderListener final : public nsIStreamLoaderObserver + , public nsIRequestObserver +{ +public: + NS_DECL_ISUPPORTS + + LoaderListener(ScriptLoaderRunnable* aRunnable, uint32_t aIndex) + : mRunnable(aRunnable) + , mIndex(aIndex) + { + MOZ_ASSERT(mRunnable); + } + + NS_IMETHOD + OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext, + nsresult aStatus, uint32_t aStringLen, + const uint8_t* aString) override; + + NS_IMETHOD + OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) override; + + NS_IMETHOD + OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, + nsresult aStatusCode) override + { + // Nothing to do here! + return NS_OK; + } + +private: + ~LoaderListener() {} + + RefPtr<ScriptLoaderRunnable> mRunnable; + uint32_t mIndex; +}; + +NS_IMPL_ISUPPORTS(LoaderListener, nsIStreamLoaderObserver, nsIRequestObserver) + +class ScriptLoaderHolder; + +class ScriptLoaderRunnable final : public nsIRunnable +{ + friend class ScriptExecutorRunnable; + friend class ScriptLoaderHolder; + friend class CachePromiseHandler; + friend class CacheScriptLoader; + friend class LoaderListener; + + WorkerPrivate* mWorkerPrivate; + nsCOMPtr<nsIEventTarget> mSyncLoopTarget; + nsTArray<ScriptLoadInfo> mLoadInfos; + RefPtr<CacheCreator> mCacheCreator; + bool mIsMainScript; + WorkerScriptType mWorkerScriptType; + bool mCanceled; + bool mCanceledMainThread; + ErrorResult& mRv; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate, + nsIEventTarget* aSyncLoopTarget, + nsTArray<ScriptLoadInfo>& aLoadInfos, + bool aIsMainScript, + WorkerScriptType aWorkerScriptType, + ErrorResult& aRv) + : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget), + mIsMainScript(aIsMainScript), mWorkerScriptType(aWorkerScriptType), + mCanceled(false), mCanceledMainThread(false), mRv(aRv) + { + aWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(aSyncLoopTarget); + MOZ_ASSERT_IF(aIsMainScript, aLoadInfos.Length() == 1); + + mLoadInfos.SwapElements(aLoadInfos); + } + +private: + ~ScriptLoaderRunnable() + { } + + NS_IMETHOD + Run() override + { + AssertIsOnMainThread(); + + nsresult rv = RunInternal(); + if (NS_WARN_IF(NS_FAILED(rv))) { + CancelMainThread(rv); + } + + return NS_OK; + } + + void + LoadingFinished(uint32_t aIndex, nsresult aRv) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aIndex < mLoadInfos.Length()); + ScriptLoadInfo& loadInfo = mLoadInfos[aIndex]; + + loadInfo.mLoadResult = aRv; + + MOZ_ASSERT(!loadInfo.mLoadingFinished); + loadInfo.mLoadingFinished = true; + + MaybeExecuteFinishedScripts(aIndex); + } + + void + MaybeExecuteFinishedScripts(uint32_t aIndex) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aIndex < mLoadInfos.Length()); + ScriptLoadInfo& loadInfo = mLoadInfos[aIndex]; + + // We execute the last step if we don't have a pending operation with the + // cache and the loading is completed. + if (loadInfo.Finished()) { + ExecuteFinishedScripts(); + } + } + + nsresult + OnStreamComplete(nsIStreamLoader* aLoader, uint32_t aIndex, + nsresult aStatus, uint32_t aStringLen, + const uint8_t* aString) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aIndex < mLoadInfos.Length()); + + nsresult rv = OnStreamCompleteInternal(aLoader, aStatus, aStringLen, + aString, mLoadInfos[aIndex]); + LoadingFinished(aIndex, rv); + return NS_OK; + } + + nsresult + OnStartRequest(nsIRequest* aRequest, uint32_t aIndex) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aIndex < mLoadInfos.Length()); + + // If one load info cancels or hits an error, it can race with the start + // callback coming from another load info. + if (mCanceledMainThread || !mCacheCreator) { + aRequest->Cancel(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + + ScriptLoadInfo& loadInfo = mLoadInfos[aIndex]; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + MOZ_ASSERT(channel == loadInfo.mChannel); + + // We synthesize the result code, but its never exposed to content. + RefPtr<mozilla::dom::InternalResponse> ir = + new mozilla::dom::InternalResponse(200, NS_LITERAL_CSTRING("OK")); + ir->SetBody(loadInfo.mCacheReadStream, InternalResponse::UNKNOWN_BODY_SIZE); + // Drop our reference to the stream now that we've passed it along, so it + // doesn't hang around once the cache is done with it and keep data alive. + loadInfo.mCacheReadStream = nullptr; + + // Set the channel info of the channel on the response so that it's + // saved in the cache. + ir->InitChannelInfo(channel); + + // Save the principal of the channel since its URI encodes the script URI + // rather than the ServiceWorkerRegistrationInfo URI. + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(ssm, "Should never be null!"); + + nsCOMPtr<nsIPrincipal> channelPrincipal; + nsresult rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal)); + if (NS_WARN_IF(NS_FAILED(rv))) { + channel->Cancel(rv); + return rv; + } + + UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo()); + rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + channel->Cancel(rv); + return rv; + } + + ir->SetPrincipalInfo(Move(principalInfo)); + + RefPtr<mozilla::dom::Response> response = + new mozilla::dom::Response(mCacheCreator->Global(), ir); + + mozilla::dom::RequestOrUSVString request; + + MOZ_ASSERT(!loadInfo.mFullURL.IsEmpty()); + request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(), + loadInfo.mFullURL.Length()); + + ErrorResult error; + RefPtr<Promise> cachePromise = + mCacheCreator->Cache_()->Put(request, *response, error); + if (NS_WARN_IF(error.Failed())) { + nsresult rv = error.StealNSResult(); + channel->Cancel(rv); + return rv; + } + + RefPtr<CachePromiseHandler> promiseHandler = + new CachePromiseHandler(this, loadInfo, aIndex); + cachePromise->AppendNativeHandler(promiseHandler); + + loadInfo.mCachePromise.swap(cachePromise); + loadInfo.mCacheStatus = ScriptLoadInfo::WritingToCache; + + return NS_OK; + } + + bool + Notify(Status aStatus) + { + mWorkerPrivate->AssertIsOnWorkerThread(); + + if (aStatus >= Terminating && !mCanceled) { + mCanceled = true; + + MOZ_ALWAYS_SUCCEEDS( + NS_DispatchToMainThread(NewRunnableMethod(this, + &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted))); + } + + return true; + } + + bool + IsMainWorkerScript() const + { + return mIsMainScript && mWorkerScriptType == WorkerScript; + } + + void + CancelMainThreadWithBindingAborted() + { + CancelMainThread(NS_BINDING_ABORTED); + } + + void + CancelMainThread(nsresult aCancelResult) + { + AssertIsOnMainThread(); + + if (mCanceledMainThread) { + return; + } + + mCanceledMainThread = true; + + if (mCacheCreator) { + MOZ_ASSERT(mWorkerPrivate->IsServiceWorker()); + DeleteCache(); + } + + // Cancel all the channels that were already opened. + for (uint32_t index = 0; index < mLoadInfos.Length(); index++) { + ScriptLoadInfo& loadInfo = mLoadInfos[index]; + + // If promise or channel is non-null, their failures will lead to + // LoadingFinished being called. + bool callLoadingFinished = true; + + if (loadInfo.mCachePromise) { + MOZ_ASSERT(mWorkerPrivate->IsServiceWorker()); + loadInfo.mCachePromise->MaybeReject(aCancelResult); + loadInfo.mCachePromise = nullptr; + callLoadingFinished = false; + } + + if (loadInfo.mChannel) { + if (NS_SUCCEEDED(loadInfo.mChannel->Cancel(aCancelResult))) { + callLoadingFinished = false; + } else { + NS_WARNING("Failed to cancel channel!"); + } + } + + if (callLoadingFinished && !loadInfo.Finished()) { + LoadingFinished(index, aCancelResult); + } + } + + ExecuteFinishedScripts(); + } + + void + DeleteCache() + { + AssertIsOnMainThread(); + + if (!mCacheCreator) { + return; + } + + mCacheCreator->DeleteCache(); + mCacheCreator = nullptr; + } + + nsresult + RunInternal() + { + AssertIsOnMainThread(); + + if (IsMainWorkerScript() && mWorkerPrivate->IsServiceWorker()) { + mWorkerPrivate->SetLoadingWorkerScript(true); + } + + if (!mWorkerPrivate->IsServiceWorker() || + !mWorkerPrivate->LoadScriptAsPartOfLoadingServiceWorkerScript()) { + for (uint32_t index = 0, len = mLoadInfos.Length(); index < len; + ++index) { + nsresult rv = LoadScript(index); + if (NS_WARN_IF(NS_FAILED(rv))) { + LoadingFinished(index, rv); + return rv; + } + } + + return NS_OK; + } + + MOZ_ASSERT(!mCacheCreator); + mCacheCreator = new CacheCreator(mWorkerPrivate); + + for (uint32_t index = 0, len = mLoadInfos.Length(); index < len; ++index) { + RefPtr<CacheScriptLoader> loader = + new CacheScriptLoader(mWorkerPrivate, mLoadInfos[index], index, + IsMainWorkerScript(), this); + mCacheCreator->AddLoader(loader); + } + + // The worker may have a null principal on first load, but in that case its + // parent definitely will have one. + nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); + if (!principal) { + WorkerPrivate* parentWorker = mWorkerPrivate->GetParent(); + MOZ_ASSERT(parentWorker, "Must have a parent!"); + principal = parentWorker->GetPrincipal(); + } + + nsresult rv = mCacheCreator->Load(principal); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + nsresult + LoadScript(uint32_t aIndex) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aIndex < mLoadInfos.Length()); + + WorkerPrivate* parentWorker = mWorkerPrivate->GetParent(); + + // Figure out which principal to use. + nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); + nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup(); + if (!principal) { + NS_ASSERTION(parentWorker, "Must have a principal!"); + NS_ASSERTION(mIsMainScript, "Must have a principal for importScripts!"); + + principal = parentWorker->GetPrincipal(); + loadGroup = parentWorker->GetLoadGroup(); + } + NS_ASSERTION(principal, "This should never be null here!"); + MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal)); + + // Figure out our base URI. + nsCOMPtr<nsIURI> baseURI = GetBaseURI(mIsMainScript, mWorkerPrivate); + + // May be null. + nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument(); + + nsCOMPtr<nsIChannel> channel; + if (IsMainWorkerScript()) { + // May be null. + channel = mWorkerPrivate->ForgetWorkerChannel(); + } + + nsCOMPtr<nsIIOService> ios(do_GetIOService()); + + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(secMan, "This should never be null!"); + + ScriptLoadInfo& loadInfo = mLoadInfos[aIndex]; + nsresult& rv = loadInfo.mLoadResult; + + nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; + + // Get the top-level worker. + WorkerPrivate* topWorkerPrivate = mWorkerPrivate; + 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 we are loading a script for a ServiceWorker then we must not + // try to intercept it. If the interception matches the current + // ServiceWorker's scope then we could deadlock the load. + if (mWorkerPrivate->IsServiceWorker()) { + loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER; + } + + if (!channel) { + // Only top level workers' main script use the document charset for the + // script uri encoding. Otherwise, default encoding (UTF-8) is applied. + bool useDefaultEncoding = !(!parentWorker && IsMainWorkerScript()); + rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios, + secMan, loadInfo.mURL, IsMainWorkerScript(), + mWorkerScriptType, + mWorkerPrivate->ContentPolicyType(), loadFlags, + useDefaultEncoding, + getter_AddRefs(channel)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // We need to know which index we're on in OnStreamComplete so we know + // where to put the result. + RefPtr<LoaderListener> listener = new LoaderListener(this, aIndex); + + // We don't care about progress so just use the simple stream loader for + // OnStreamComplete notification only. + nsCOMPtr<nsIStreamLoader> loader; + rv = NS_NewStreamLoader(getter_AddRefs(loader), listener); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (loadInfo.mCacheStatus != ScriptLoadInfo::ToBeCached) { + rv = channel->AsyncOpen2(loader); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + nsCOMPtr<nsIOutputStream> writer; + + // In case we return early. + loadInfo.mCacheStatus = ScriptLoadInfo::Cancel; + + rv = NS_NewPipe(getter_AddRefs(loadInfo.mCacheReadStream), + getter_AddRefs(writer), 0, + UINT32_MAX, // unlimited size to avoid writer WOULD_BLOCK case + true, false); // non-blocking reader, blocking writer + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + 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->AsyncOpen2(tee); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + loadInfo.mChannel.swap(channel); + + return NS_OK; + } + + nsresult + OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsresult aStatus, + uint32_t aStringLen, const uint8_t* aString, + ScriptLoadInfo& aLoadInfo) + { + AssertIsOnMainThread(); + + if (!aLoadInfo.mChannel) { + return NS_BINDING_ABORTED; + } + + aLoadInfo.mChannel = nullptr; + + if (NS_FAILED(aStatus)) { + return aStatus; + } + + NS_ASSERTION(aString, "This should never be null!"); + + nsCOMPtr<nsIRequest> request; + nsresult rv = aLoader->GetRequest(getter_AddRefs(request)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + MOZ_ASSERT(channel); + + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(ssm, "Should never be null!"); + + nsCOMPtr<nsIPrincipal> channelPrincipal; + rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); + if (!principal) { + WorkerPrivate* parentWorker = mWorkerPrivate->GetParent(); + MOZ_ASSERT(parentWorker, "Must have a parent!"); + principal = parentWorker->GetPrincipal(); + } + + // We don't mute the main worker script becase we've already done + // same-origin checks on them so we should be able to see their errors. + // Note that for data: url, where we allow it through the same-origin check + // but then give it a different origin. + aLoadInfo.mMutedErrorFlag.emplace(IsMainWorkerScript() + ? false + : !principal->Subsumes(channelPrincipal)); + + // Make sure we're not seeing the result of a 404 or something by checking + // the 'requestSucceeded' attribute on the http channel. + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request); + nsAutoCString tCspHeaderValue, tCspROHeaderValue, tRPHeaderCValue; + + if (httpChannel) { + bool requestSucceeded; + rv = httpChannel->GetRequestSucceeded(&requestSucceeded); + NS_ENSURE_SUCCESS(rv, rv); + + if (!requestSucceeded) { + return NS_ERROR_NOT_AVAILABLE; + } + + httpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("content-security-policy"), + tCspHeaderValue); + + httpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("content-security-policy-report-only"), + tCspROHeaderValue); + + httpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("referrer-policy"), + tRPHeaderCValue); + } + + // May be null. + nsIDocument* parentDoc = mWorkerPrivate->GetDocument(); + + // Use the regular nsScriptLoader for this grunt work! Should be just fine + // because we're running on the main thread. + // Unlike <script> tags, Worker scripts are always decoded as UTF-8, + // per spec. So we explicitly pass in the charset hint. + rv = nsScriptLoader::ConvertToUTF16(aLoadInfo.mChannel, aString, aStringLen, + NS_LITERAL_STRING("UTF-8"), parentDoc, + aLoadInfo.mScriptTextBuf, + aLoadInfo.mScriptTextLength); + if (NS_FAILED(rv)) { + return rv; + } + + if (!aLoadInfo.mScriptTextLength && !aLoadInfo.mScriptTextBuf) { + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("DOM"), parentDoc, + nsContentUtils::eDOM_PROPERTIES, + "EmptyWorkerSourceWarning"); + } else if (!aLoadInfo.mScriptTextBuf) { + return NS_ERROR_FAILURE; + } + + // Figure out what we actually loaded. + nsCOMPtr<nsIURI> finalURI; + rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString filename; + rv = finalURI->GetSpec(filename); + NS_ENSURE_SUCCESS(rv, rv); + + if (!filename.IsEmpty()) { + // This will help callers figure out what their script url resolved to in + // case of errors. + aLoadInfo.mURL.Assign(NS_ConvertUTF8toUTF16(filename)); + } + + nsCOMPtr<nsILoadInfo> chanLoadInfo = channel->GetLoadInfo(); + if (chanLoadInfo && chanLoadInfo->GetEnforceSRI()) { + // importScripts() and the Worker constructor do not support integrity metadata + // (or any fetch options). Until then, we can just block. + // If we ever have those data in the future, we'll have to the check to + // by using the SRICheck module + MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, + ("Scriptloader::Load, SRI required but not supported in workers")); + nsCOMPtr<nsIContentSecurityPolicy> wcsp; + chanLoadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(wcsp)); + MOZ_ASSERT(wcsp, "We sould have a CSP for the worker here"); + if (wcsp) { + wcsp->LogViolationDetails( + nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_SCRIPT, + aLoadInfo.mURL, EmptyString(), 0, EmptyString(), EmptyString()); + } + return NS_ERROR_SRI_CORRUPT; + } + + // Update the principal of the worker and its base URI if we just loaded the + // worker's primary script. + if (IsMainWorkerScript()) { + // Take care of the base URI first. + mWorkerPrivate->SetBaseURI(finalURI); + + // Store the channel info if needed. + mWorkerPrivate->InitChannelInfo(channel); + + // Now to figure out which principal to give this worker. + WorkerPrivate* parent = mWorkerPrivate->GetParent(); + + NS_ASSERTION(mWorkerPrivate->GetPrincipal() || parent, + "Must have one of these!"); + + nsCOMPtr<nsIPrincipal> loadPrincipal = mWorkerPrivate->GetPrincipal() ? + mWorkerPrivate->GetPrincipal() : + parent->GetPrincipal(); + + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(ssm, "Should never be null!"); + + nsCOMPtr<nsIPrincipal> channelPrincipal; + rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILoadGroup> channelLoadGroup; + rv = channel->GetLoadGroup(getter_AddRefs(channelLoadGroup)); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(channelLoadGroup); + + // If the load principal is the system principal then the channel + // principal must also be the system principal (we do not allow chrome + // code to create workers with non-chrome scripts, and if we ever decide + // to change this we need to make sure we don't always set + // mPrincipalIsSystem to true in WorkerPrivate::GetLoadInfo()). Otherwise + // this channel principal must be same origin with the load principal (we + // check again here in case redirects changed the location of the script). + if (nsContentUtils::IsSystemPrincipal(loadPrincipal)) { + if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) { + // See if this is a resource URI. Since JSMs usually come from + // resource:// URIs we're currently considering all URIs with the + // URI_IS_UI_RESOURCE flag as valid for creating privileged workers. + bool isResource; + rv = NS_URIChainHasFlags(finalURI, + nsIProtocolHandler::URI_IS_UI_RESOURCE, + &isResource); + NS_ENSURE_SUCCESS(rv, rv); + + if (isResource) { + // Assign the system principal to the resource:// worker only if it + // was loaded from code using the system principal. + channelPrincipal = loadPrincipal; + } else { + return NS_ERROR_DOM_BAD_URI; + } + } + } + + // The principal can change, but it should still match the original + // load group's appId and browser element flag. + MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(channelLoadGroup, channelPrincipal)); + + mWorkerPrivate->SetPrincipal(channelPrincipal, channelLoadGroup); + + // We did inherit CSP in bug 1223647. If we do not already have a CSP, we + // should get it from the HTTP headers on the worker script. + if (!mWorkerPrivate->GetCSP() && CSPService::sCSPEnabled) { + NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue); + NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue); + + nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); + MOZ_ASSERT(principal, "Should not be null"); + + nsCOMPtr<nsIContentSecurityPolicy> csp; + rv = principal->EnsureCSP(nullptr, getter_AddRefs(csp)); + + if (csp) { + // If there's a CSP header, apply it. + if (!cspHeaderValue.IsEmpty()) { + rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false); + NS_ENSURE_SUCCESS(rv, rv); + } + // If there's a report-only CSP header, apply it. + if (!cspROHeaderValue.IsEmpty()) { + rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Set evalAllowed, default value is set in GetAllowsEval + bool evalAllowed = false; + bool reportEvalViolations = false; + rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed); + NS_ENSURE_SUCCESS(rv, rv); + + mWorkerPrivate->SetCSP(csp); + mWorkerPrivate->SetEvalAllowed(evalAllowed); + mWorkerPrivate->SetReportCSPViolations(reportEvalViolations); + + // Set ReferrerPolicy, default value is set in GetReferrerPolicy + bool hasReferrerPolicy = false; + uint32_t rp = mozilla::net::RP_Default; + rv = csp->GetReferrerPolicy(&rp, &hasReferrerPolicy); + NS_ENSURE_SUCCESS(rv, rv); + + + if (hasReferrerPolicy) { //FIXME bug 1307366: move RP out of CSP code + mWorkerPrivate->SetReferrerPolicy(static_cast<net::ReferrerPolicy>(rp)); + } + } + } + if (parent) { + // XHR Params Allowed + mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed()); + } + } + + NS_ConvertUTF8toUTF16 tRPHeaderValue(tRPHeaderCValue); + // If there's a Referrer-Policy header, apply it. + if (!tRPHeaderValue.IsEmpty()) { + net::ReferrerPolicy policy = + nsContentUtils::GetReferrerPolicyFromHeader(tRPHeaderValue); + if (policy != net::RP_Unset) { + mWorkerPrivate->SetReferrerPolicy(policy); + } + } + + return NS_OK; + } + + void + DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString, + uint32_t aStringLen, + const mozilla::dom::ChannelInfo& aChannelInfo, + UniquePtr<PrincipalInfo> aPrincipalInfo) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aIndex < mLoadInfos.Length()); + ScriptLoadInfo& loadInfo = mLoadInfos[aIndex]; + MOZ_ASSERT(loadInfo.mCacheStatus == ScriptLoadInfo::Cached); + + nsCOMPtr<nsIPrincipal> responsePrincipal = + PrincipalInfoToPrincipal(*aPrincipalInfo); + + nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); + if (!principal) { + WorkerPrivate* parentWorker = mWorkerPrivate->GetParent(); + MOZ_ASSERT(parentWorker, "Must have a parent!"); + principal = parentWorker->GetPrincipal(); + } + + loadInfo.mMutedErrorFlag.emplace(!principal->Subsumes(responsePrincipal)); + + // May be null. + nsIDocument* parentDoc = mWorkerPrivate->GetDocument(); + + MOZ_ASSERT(!loadInfo.mScriptTextBuf); + + nsresult rv = + nsScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen, + NS_LITERAL_STRING("UTF-8"), parentDoc, + loadInfo.mScriptTextBuf, + loadInfo.mScriptTextLength); + if (NS_SUCCEEDED(rv) && IsMainWorkerScript()) { + nsCOMPtr<nsIURI> finalURI; + rv = NS_NewURI(getter_AddRefs(finalURI), loadInfo.mFullURL, nullptr, nullptr); + if (NS_SUCCEEDED(rv)) { + mWorkerPrivate->SetBaseURI(finalURI); + } + + mozilla::DebugOnly<nsIPrincipal*> principal = mWorkerPrivate->GetPrincipal(); + MOZ_ASSERT(principal); + nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup(); + MOZ_ASSERT(loadGroup); + + mozilla::DebugOnly<bool> equal = false; + MOZ_ASSERT(responsePrincipal && NS_SUCCEEDED(responsePrincipal->Equals(principal, &equal))); + MOZ_ASSERT(equal); + + mWorkerPrivate->InitChannelInfo(aChannelInfo); + mWorkerPrivate->SetPrincipal(responsePrincipal, loadGroup); + } + + if (NS_SUCCEEDED(rv)) { + DataReceived(); + } + + LoadingFinished(aIndex, rv); + } + + void + DataReceived() + { + if (IsMainWorkerScript()) { + WorkerPrivate* parent = mWorkerPrivate->GetParent(); + + if (parent) { + // XHR Params Allowed + mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed()); + + // Set Eval and ContentSecurityPolicy + mWorkerPrivate->SetCSP(parent->GetCSP()); + mWorkerPrivate->SetEvalAllowed(parent->IsEvalAllowed()); + } + } + } + + void + ExecuteFinishedScripts() + { + AssertIsOnMainThread(); + + if (IsMainWorkerScript()) { + mWorkerPrivate->WorkerScriptLoaded(); + } + + uint32_t firstIndex = UINT32_MAX; + uint32_t lastIndex = UINT32_MAX; + + // Find firstIndex based on whether mExecutionScheduled is unset. + for (uint32_t index = 0; index < mLoadInfos.Length(); index++) { + if (!mLoadInfos[index].mExecutionScheduled) { + firstIndex = index; + break; + } + } + + // Find lastIndex based on whether mChannel is set, and update + // mExecutionScheduled on the ones we're about to schedule. + if (firstIndex != UINT32_MAX) { + for (uint32_t index = firstIndex; index < mLoadInfos.Length(); index++) { + ScriptLoadInfo& loadInfo = mLoadInfos[index]; + + if (!loadInfo.Finished()) { + break; + } + + // We can execute this one. + loadInfo.mExecutionScheduled = true; + + lastIndex = index; + } + } + + // This is the last index, we can unused things before the exection of the + // script and the stopping of the sync loop. + if (lastIndex == mLoadInfos.Length() - 1) { + mCacheCreator = nullptr; + } + + if (firstIndex != UINT32_MAX && lastIndex != UINT32_MAX) { + RefPtr<ScriptExecutorRunnable> runnable = + new ScriptExecutorRunnable(*this, mSyncLoopTarget, IsMainWorkerScript(), + firstIndex, lastIndex); + if (!runnable->Dispatch()) { + MOZ_ASSERT(false, "This should never fail!"); + } + } + } +}; + +NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable) + +class MOZ_STACK_CLASS ScriptLoaderHolder final : public WorkerHolder +{ + // Raw pointer because this holder object follows the mRunnable life-time. + ScriptLoaderRunnable* mRunnable; + +public: + explicit ScriptLoaderHolder(ScriptLoaderRunnable* aRunnable) + : mRunnable(aRunnable) + { + MOZ_ASSERT(aRunnable); + } + + virtual bool + Notify(Status aStatus) override + { + mRunnable->Notify(aStatus); + return true; + } +}; + +NS_IMETHODIMP +LoaderListener::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext, + nsresult aStatus, uint32_t aStringLen, + const uint8_t* aString) +{ + return mRunnable->OnStreamComplete(aLoader, mIndex, aStatus, aStringLen, aString); +} + +NS_IMETHODIMP +LoaderListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) +{ + return mRunnable->OnStartRequest(aRequest, mIndex); +} + +void +CachePromiseHandler::ResolvedCallback(JSContext* aCx, + JS::Handle<JS::Value> aValue) +{ + AssertIsOnMainThread(); + // May already have been canceled by CacheScriptLoader::Fail from + // CancelMainThread. + MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache || + mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel); + MOZ_ASSERT_IF(mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel, !mLoadInfo.mCachePromise); + + if (mLoadInfo.mCachePromise) { + mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached; + mLoadInfo.mCachePromise = nullptr; + mRunnable->MaybeExecuteFinishedScripts(mIndex); + } +} + +void +CachePromiseHandler::RejectedCallback(JSContext* aCx, + JS::Handle<JS::Value> aValue) +{ + AssertIsOnMainThread(); + // May already have been canceled by CacheScriptLoader::Fail from + // CancelMainThread. + MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache || + mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel); + mLoadInfo.mCacheStatus = ScriptLoadInfo::Cancel; + + mLoadInfo.mCachePromise = nullptr; + + // This will delete the cache object and will call LoadingFinished() with an + // error for each ongoing operation. + mRunnable->DeleteCache(); +} + +nsresult +CacheCreator::CreateCacheStorage(nsIPrincipal* aPrincipal) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(!mCacheStorage); + MOZ_ASSERT(aPrincipal); + + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + MOZ_ASSERT(xpc, "This should never be null!"); + + mozilla::AutoSafeJSContext cx; + JS::Rooted<JSObject*> sandbox(cx); + nsresult rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mSandboxGlobalObject = xpc::NativeGlobal(sandbox); + if (NS_WARN_IF(!mSandboxGlobalObject)) { + return NS_ERROR_FAILURE; + } + + // If we're in private browsing mode, don't even try to create the + // CacheStorage. Instead, just fail immediately to terminate the + // ServiceWorker load. + if (NS_WARN_IF(mOriginAttributes.mPrivateBrowsingId > 0)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + // Create a CacheStorage bypassing its trusted origin checks. The + // ServiceWorker has already performed its own checks before getting + // to this point. + ErrorResult error; + mCacheStorage = + CacheStorage::CreateOnMainThread(mozilla::dom::cache::CHROME_ONLY_NAMESPACE, + mSandboxGlobalObject, + aPrincipal, + false, /* privateBrowsing can't be true here */ + true /* force trusted origin */, + error); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } + + return NS_OK; +} + +nsresult +CacheCreator::Load(nsIPrincipal* aPrincipal) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(!mLoaders.IsEmpty()); + + nsresult rv = CreateCacheStorage(aPrincipal); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + ErrorResult error; + MOZ_ASSERT(!mCacheName.IsEmpty()); + RefPtr<Promise> promise = mCacheStorage->Open(mCacheName, error); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } + + promise->AppendNativeHandler(this); + return NS_OK; +} + +void +CacheCreator::FailLoaders(nsresult aRv) +{ + AssertIsOnMainThread(); + + // Fail() can call LoadingFinished() which may call ExecuteFinishedScripts() + // which sets mCacheCreator to null, so hold a ref. + RefPtr<CacheCreator> kungfuDeathGrip = this; + + for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) { + mLoaders[i]->Fail(aRv); + } + + mLoaders.Clear(); +} + +void +CacheCreator::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) +{ + AssertIsOnMainThread(); + FailLoaders(NS_ERROR_FAILURE); +} + +void +CacheCreator::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) +{ + AssertIsOnMainThread(); + + if (!aValue.isObject()) { + FailLoaders(NS_ERROR_FAILURE); + return; + } + + JS::Rooted<JSObject*> obj(aCx, &aValue.toObject()); + Cache* cache = nullptr; + nsresult rv = UNWRAP_OBJECT(Cache, &obj, cache); + if (NS_WARN_IF(NS_FAILED(rv))) { + FailLoaders(NS_ERROR_FAILURE); + return; + } + + mCache = cache; + MOZ_DIAGNOSTIC_ASSERT(mCache); + + // If the worker is canceled, CancelMainThread() will have cleared the + // loaders via DeleteCache(). + for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) { + MOZ_DIAGNOSTIC_ASSERT(mLoaders[i]); + mLoaders[i]->Load(cache); + } +} + +void +CacheCreator::DeleteCache() +{ + AssertIsOnMainThread(); + + // This is called when the load is canceled which can occur before + // mCacheStorage is initialized. + if (mCacheStorage) { + // It's safe to do this while Cache::Match() and Cache::Put() calls are + // running. + IgnoredErrorResult rv; + RefPtr<Promise> promise = mCacheStorage->Delete(mCacheName, rv); + + // We don't care to know the result of the promise object. + } + + // Always call this here to ensure the loaders array is cleared. + FailLoaders(NS_ERROR_FAILURE); +} + +void +CacheScriptLoader::Fail(nsresult aRv) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(NS_FAILED(aRv)); + + if (mFailed) { + return; + } + + mFailed = true; + + if (mPump) { + MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache); + mPump->Cancel(aRv); + mPump = nullptr; + } + + mLoadInfo.mCacheStatus = ScriptLoadInfo::Cancel; + + // Stop if the load was aborted on the main thread. + // Can't use Finished() because mCachePromise may still be true. + if (mLoadInfo.mLoadingFinished) { + MOZ_ASSERT(!mLoadInfo.mChannel); + MOZ_ASSERT_IF(mLoadInfo.mCachePromise, + mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache || + mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel); + return; + } + + mRunnable->LoadingFinished(mIndex, aRv); +} + +void +CacheScriptLoader::Load(Cache* aCache) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aCache); + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), mLoadInfo.mURL, nullptr, + mBaseURI); + if (NS_WARN_IF(NS_FAILED(rv))) { + Fail(rv); + return; + } + + nsAutoCString spec; + rv = uri->GetSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + Fail(rv); + return; + } + + MOZ_ASSERT(mLoadInfo.mFullURL.IsEmpty()); + CopyUTF8toUTF16(spec, mLoadInfo.mFullURL); + + mozilla::dom::RequestOrUSVString request; + request.SetAsUSVString().Rebind(mLoadInfo.mFullURL.Data(), + mLoadInfo.mFullURL.Length()); + + mozilla::dom::CacheQueryOptions params; + + ErrorResult error; + RefPtr<Promise> promise = aCache->Match(request, params, error); + if (NS_WARN_IF(error.Failed())) { + Fail(error.StealNSResult()); + return; + } + + promise->AppendNativeHandler(this); +} + +void +CacheScriptLoader::RejectedCallback(JSContext* aCx, + JS::Handle<JS::Value> aValue) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::Uncached); + Fail(NS_ERROR_FAILURE); +} + +void +CacheScriptLoader::ResolvedCallback(JSContext* aCx, + JS::Handle<JS::Value> aValue) +{ + AssertIsOnMainThread(); + // If we have already called 'Fail', we should not proceed. + if (mFailed) { + return; + } + + MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::Uncached); + + nsresult rv; + + if (aValue.isUndefined()) { + mLoadInfo.mCacheStatus = ScriptLoadInfo::ToBeCached; + rv = mRunnable->LoadScript(mIndex); + if (NS_WARN_IF(NS_FAILED(rv))) { + Fail(rv); + } + return; + } + + MOZ_ASSERT(aValue.isObject()); + + JS::Rooted<JSObject*> obj(aCx, &aValue.toObject()); + mozilla::dom::Response* response = nullptr; + rv = UNWRAP_OBJECT(Response, &obj, response); + if (NS_WARN_IF(NS_FAILED(rv))) { + Fail(rv); + return; + } + + nsCOMPtr<nsIInputStream> inputStream; + response->GetBody(getter_AddRefs(inputStream)); + mChannelInfo = response->GetChannelInfo(); + const UniquePtr<PrincipalInfo>& pInfo = response->GetPrincipalInfo(); + if (pInfo) { + mPrincipalInfo = mozilla::MakeUnique<PrincipalInfo>(*pInfo); + } + + if (!inputStream) { + mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached; + mRunnable->DataReceivedFromCache(mIndex, (uint8_t*)"", 0, mChannelInfo, + Move(mPrincipalInfo)); + return; + } + + MOZ_ASSERT(!mPump); + rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream); + if (NS_WARN_IF(NS_FAILED(rv))) { + Fail(rv); + return; + } + + nsCOMPtr<nsIStreamLoader> loader; + rv = NS_NewStreamLoader(getter_AddRefs(loader), this); + if (NS_WARN_IF(NS_FAILED(rv))) { + Fail(rv); + return; + } + + rv = mPump->AsyncRead(loader, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + mPump = nullptr; + Fail(rv); + return; + } + + + nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump); + if (rr) { + nsCOMPtr<nsIEventTarget> sts = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + rv = rr->RetargetDeliveryTo(sts); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread."); + } + } + + mLoadInfo.mCacheStatus = ScriptLoadInfo::ReadingFromCache; +} + +NS_IMETHODIMP +CacheScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext, + nsresult aStatus, uint32_t aStringLen, + const uint8_t* aString) +{ + AssertIsOnMainThread(); + + mPump = nullptr; + + if (NS_FAILED(aStatus)) { + MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache || + mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel); + Fail(aStatus); + return NS_OK; + } + + MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache); + mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached; + + MOZ_ASSERT(mPrincipalInfo); + mRunnable->DataReceivedFromCache(mIndex, aString, aStringLen, mChannelInfo, + Move(mPrincipalInfo)); + return NS_OK; +} + +class ChannelGetterRunnable final : public WorkerMainThreadRunnable +{ + const nsAString& mScriptURL; + nsIChannel** mChannel; + nsresult mResult; + +public: + ChannelGetterRunnable(WorkerPrivate* aParentWorker, + const nsAString& aScriptURL, + nsIChannel** aChannel) + : WorkerMainThreadRunnable(aParentWorker, + NS_LITERAL_CSTRING("ScriptLoader :: ChannelGetter")) + , mScriptURL(aScriptURL) + , mChannel(aChannel) + , mResult(NS_ERROR_FAILURE) + { + MOZ_ASSERT(aParentWorker); + aParentWorker->AssertIsOnWorkerThread(); + } + + virtual bool + MainThreadRun() override + { + AssertIsOnMainThread(); + + nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); + MOZ_ASSERT(principal); + + // Figure out our base URI. + nsCOMPtr<nsIURI> baseURI = mWorkerPrivate->GetBaseURI(); + MOZ_ASSERT(baseURI); + + // May be null. + nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument(); + + nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup(); + + nsCOMPtr<nsIChannel> channel; + mResult = + scriptloader::ChannelFromScriptURLMainThread(principal, baseURI, + parentDoc, loadGroup, + mScriptURL, + // Nested workers are always dedicated. + nsIContentPolicy::TYPE_INTERNAL_WORKER, + // Nested workers use default uri encoding. + true, + getter_AddRefs(channel)); + if (NS_SUCCEEDED(mResult)) { + channel.forget(mChannel); + } + + return true; + } + + nsresult + GetResult() const + { + return mResult; + } + +private: + virtual ~ChannelGetterRunnable() + { } +}; + +ScriptExecutorRunnable::ScriptExecutorRunnable( + ScriptLoaderRunnable& aScriptLoader, + nsIEventTarget* aSyncLoopTarget, + bool aIsWorkerScript, + uint32_t aFirstIndex, + uint32_t aLastIndex) +: MainThreadWorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncLoopTarget), + mScriptLoader(aScriptLoader), mIsWorkerScript(aIsWorkerScript), + mFirstIndex(aFirstIndex), mLastIndex(aLastIndex) +{ + MOZ_ASSERT(aFirstIndex <= aLastIndex); + MOZ_ASSERT(aLastIndex < aScriptLoader.mLoadInfos.Length()); +} + +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.mWorkerScriptType == DebuggerScript; +} + +bool +ScriptExecutorRunnable::PreRun(WorkerPrivate* aWorkerPrivate) +{ + aWorkerPrivate->AssertIsOnWorkerThread(); + + if (!mIsWorkerScript) { + return true; + } + + if (!aWorkerPrivate->GetJSContext()) { + return false; + } + + MOZ_ASSERT(mFirstIndex == 0); + MOZ_ASSERT(!mScriptLoader.mRv.Failed()); + + AutoJSAPI jsapi; + jsapi.Init(); + + WorkerGlobalScope* globalScope = + aWorkerPrivate->GetOrCreateGlobalScope(jsapi.cx()); + if (NS_WARN_IF(!globalScope)) { + NS_WARNING("Failed to make global!"); + // There's no way to report the exception on jsapi right now, because there + // is no way to even enter a compartment on this thread anymore. Just clear + // the exception. We'll report some sort of error to our caller in + // ShutdownScriptLoader, but it will get squelched for the same reason we're + // squelching here: all the error reporting machinery relies on being able + // to enter a compartment to report the error. + jsapi.ClearException(); + return false; + } + + return true; +} + +bool +ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +{ + aWorkerPrivate->AssertIsOnWorkerThread(); + + nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos; + + // Don't run if something else has already failed. + for (uint32_t index = 0; index < mFirstIndex; index++) { + ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index); + + NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!"); + NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!"); + + if (!loadInfo.mExecutionResult) { + return true; + } + } + + // If nothing else has failed, our ErrorResult better not be a failure either. + MOZ_ASSERT(!mScriptLoader.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); + + for (uint32_t index = mFirstIndex; index <= mLastIndex; index++) { + ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index); + + NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!"); + NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!"); + NS_ASSERTION(!loadInfo.mExecutionResult, "Should not have executed yet!"); + + MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?"); + mScriptLoader.mRv.MightThrowJSException(); + if (NS_FAILED(loadInfo.mLoadResult)) { + scriptloader::ReportLoadError(mScriptLoader.mRv, + loadInfo.mLoadResult, loadInfo.mURL); + // Top level scripts only! + if (mIsWorkerScript) { + aWorkerPrivate->MaybeDispatchLoadFailedRunnable(); + } + return true; + } + + NS_ConvertUTF16toUTF8 filename(loadInfo.mURL); + + JS::CompileOptions options(aCx); + options.setFileAndLine(filename.get(), 1) + .setNoScriptRval(true); + + if (mScriptLoader.mWorkerScriptType == DebuggerScript) { + options.setVersion(JSVERSION_LATEST); + } + + MOZ_ASSERT(loadInfo.mMutedErrorFlag.isSome()); + options.setMutedErrors(loadInfo.mMutedErrorFlag.valueOr(true)); + + JS::SourceBufferHolder srcBuf(loadInfo.mScriptTextBuf, + loadInfo.mScriptTextLength, + JS::SourceBufferHolder::GiveOwnership); + loadInfo.mScriptTextBuf = nullptr; + loadInfo.mScriptTextLength = 0; + + // Our ErrorResult still shouldn't be a failure. + MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?"); + JS::Rooted<JS::Value> unused(aCx); + if (!JS::Evaluate(aCx, options, srcBuf, &unused)) { + mScriptLoader.mRv.StealExceptionFromJSContext(aCx); + return true; + } + + loadInfo.mExecutionResult = true; + } + + return true; +} + +void +ScriptExecutorRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aRunResult) +{ + aWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(!JS_IsExceptionPending(aCx), "Who left an exception on there?"); + + nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos; + + if (mLastIndex == loadInfos.Length() - 1) { + // All done. If anything failed then return false. + bool result = true; + bool mutedError = false; + for (uint32_t index = 0; index < loadInfos.Length(); index++) { + if (!loadInfos[index].mExecutionResult) { + mutedError = loadInfos[index].mMutedErrorFlag.valueOr(true); + result = false; + break; + } + } + + // The only way we can get here with "result" false but without + // mScriptLoader.mRv being a failure is if we're loading the main worker + // script and GetOrCreateGlobalScope() fails. In that case we would have + // returned false from WorkerRun, so assert that. + MOZ_ASSERT_IF(!result && !mScriptLoader.mRv.Failed(), + !aRunResult); + ShutdownScriptLoader(aCx, aWorkerPrivate, result, mutedError); + } +} + +nsresult +ScriptExecutorRunnable::Cancel() +{ + if (mLastIndex == mScriptLoader.mLoadInfos.Length() - 1) { + ShutdownScriptLoader(mWorkerPrivate->GetJSContext(), mWorkerPrivate, + false, false); + } + return MainThreadWorkerSyncRunnable::Cancel(); +} + +void +ScriptExecutorRunnable::ShutdownScriptLoader(JSContext* aCx, + WorkerPrivate* aWorkerPrivate, + bool aResult, + bool aMutedError) +{ + aWorkerPrivate->AssertIsOnWorkerThread(); + + MOZ_ASSERT(mLastIndex == mScriptLoader.mLoadInfos.Length() - 1); + + if (mIsWorkerScript && aWorkerPrivate->IsServiceWorker()) { + aWorkerPrivate->SetLoadingWorkerScript(false); + } + + if (!aResult) { + // At this point there are two possibilities: + // + // 1) mScriptLoader.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) mScriptLoader.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 (mScriptLoader.mRv.Failed()) { + if (aMutedError && mScriptLoader.mRv.IsJSException()) { + LogExceptionToConsole(aCx, aWorkerPrivate); + mScriptLoader.mRv.Throw(NS_ERROR_DOM_NETWORK_ERR); + } + } else { + mScriptLoader.mRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + } + } + + aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, aResult); +} + +void +ScriptExecutorRunnable::LogExceptionToConsole(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) +{ + aWorkerPrivate->AssertIsOnWorkerThread(); + + MOZ_ASSERT(mScriptLoader.mRv.IsJSException()); + + JS::Rooted<JS::Value> exn(aCx); + if (!ToJSValue(aCx, mScriptLoader.mRv, &exn)) { + return; + } + + // Now the exception state should all be in exn. + MOZ_ASSERT(!JS_IsExceptionPending(aCx)); + MOZ_ASSERT(!mScriptLoader.mRv.Failed()); + + js::ErrorReport report(aCx); + if (!report.init(aCx, exn, js::ErrorReport::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); +} + +void +LoadAllScripts(WorkerPrivate* aWorkerPrivate, + nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsMainScript, + WorkerScriptType aWorkerScriptType, ErrorResult& aRv) +{ + aWorkerPrivate->AssertIsOnWorkerThread(); + NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!"); + + AutoSyncLoopHolder syncLoop(aWorkerPrivate); + + RefPtr<ScriptLoaderRunnable> loader = + new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.EventTarget(), + aLoadInfos, aIsMainScript, aWorkerScriptType, + aRv); + + NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!"); + + ScriptLoaderHolder workerHolder(loader); + + if (NS_WARN_IF(!workerHolder.HoldWorker(aWorkerPrivate, Terminating))) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + if (NS_FAILED(NS_DispatchToMainThread(loader))) { + NS_ERROR("Failed to dispatch!"); + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + syncLoop.Run(); +} + +} /* anonymous namespace */ + +BEGIN_WORKERS_NAMESPACE + +namespace scriptloader { + +nsresult +ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal, + nsIURI* aBaseURI, + nsIDocument* aParentDoc, + nsILoadGroup* aLoadGroup, + const nsAString& aScriptURL, + nsContentPolicyType aContentPolicyType, + bool aDefaultURIEncoding, + nsIChannel** aChannel) +{ + AssertIsOnMainThread(); + + nsCOMPtr<nsIIOService> ios(do_GetIOService()); + + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(secMan, "This should never be null!"); + + return ChannelFromScriptURL(aPrincipal, aBaseURI, aParentDoc, aLoadGroup, + ios, secMan, aScriptURL, true, WorkerScript, + aContentPolicyType, nsIRequest::LOAD_NORMAL, + aDefaultURIEncoding, aChannel); +} + +nsresult +ChannelFromScriptURLWorkerThread(JSContext* aCx, + WorkerPrivate* aParent, + const nsAString& aScriptURL, + nsIChannel** aChannel) +{ + aParent->AssertIsOnWorkerThread(); + + RefPtr<ChannelGetterRunnable> getter = + new ChannelGetterRunnable(aParent, aScriptURL, aChannel); + + ErrorResult rv; + getter->Dispatch(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()); + + switch (aLoadResult) { + case NS_ERROR_FILE_NOT_FOUND: + case NS_ERROR_NOT_AVAILABLE: + aLoadResult = NS_ERROR_DOM_NETWORK_ERR; + break; + + case NS_ERROR_MALFORMED_URI: + aLoadResult = NS_ERROR_DOM_SYNTAX_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 ThrowDOMException() + // 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_SECURITY_ERR: + case NS_ERROR_DOM_SYNTAX_ERR: + break; + + case NS_ERROR_DOM_BAD_URI: + // This is actually a security error. + aLoadResult = NS_ERROR_DOM_SECURITY_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.ThrowDOMException(NS_ERROR_DOM_NETWORK_ERR, + nsPrintfCString("Failed to load worker script at %s (nsresult = 0x%x)", + NS_ConvertUTF16toUTF8(aScriptURL).get(), + aLoadResult)); + return; + } + + aRv.ThrowDOMException(aLoadResult, + NS_LITERAL_CSTRING("Failed to load worker script at \"") + + NS_ConvertUTF16toUTF8(aScriptURL) + + NS_LITERAL_CSTRING("\"")); +} + +void +LoadMainScript(WorkerPrivate* aWorkerPrivate, + const nsAString& aScriptURL, + WorkerScriptType aWorkerScriptType, + ErrorResult& aRv) +{ + nsTArray<ScriptLoadInfo> loadInfos; + + ScriptLoadInfo* info = loadInfos.AppendElement(); + info->mURL = aScriptURL; + + LoadAllScripts(aWorkerPrivate, loadInfos, true, aWorkerScriptType, aRv); +} + +void +Load(WorkerPrivate* aWorkerPrivate, + 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; + } + + nsTArray<ScriptLoadInfo> loadInfos; + loadInfos.SetLength(urlCount); + + for (uint32_t index = 0; index < urlCount; index++) { + loadInfos[index].mURL = aScriptURLs[index]; + } + + LoadAllScripts(aWorkerPrivate, loadInfos, false, aWorkerScriptType, aRv); +} + +} // namespace scriptloader + +END_WORKERS_NAMESPACE |