summaryrefslogtreecommitdiffstats
path: root/dom/workers/ScriptLoader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/workers/ScriptLoader.cpp')
-rw-r--r--dom/workers/ScriptLoader.cpp2290
1 files changed, 2290 insertions, 0 deletions
diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp
new file mode 100644
index 000000000..46545e737
--- /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