/* -*- 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