/* -*- 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 "XMLHttpRequestWorker.h"

#include "nsIDOMEvent.h"
#include "nsIDOMEventListener.h"
#include "nsIRunnable.h"
#include "nsIXMLHttpRequest.h"
#include "nsIXPConnect.h"

#include "jsfriendapi.h"
#include "js/TracingAPI.h"
#include "js/GCPolicyAPI.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/ProgressEvent.h"
#include "mozilla/dom/StructuredCloneHolder.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/Telemetry.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsJSUtils.h"
#include "nsThreadUtils.h"
#include "nsVariant.h"

#include "RuntimeService.h"
#include "WorkerScope.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "XMLHttpRequestUpload.h"

#include "mozilla/UniquePtr.h"

namespace mozilla {
namespace dom {

using namespace workers;

/* static */ void
XMLHttpRequestWorker::StateData::trace(JSTracer *aTrc)
{
  JS::TraceEdge(aTrc, &mResponse, "XMLHttpRequestWorker::StateData::mResponse");
}

/**
 *  XMLHttpRequest in workers
 *
 *  XHR in workers is implemented by proxying calls/events/etc between the
 *  worker thread and an XMLHttpRequest on the main thread.  The glue
 *  object here is the Proxy, which lives on both threads.  All other objects
 *  live on either the main thread (the XMLHttpRequest) or the worker thread
 *  (the worker and XHR private objects).
 *
 *  The main thread XHR is always operated in async mode, even for sync XHR
 *  in workers.  Calls made on the worker thread are proxied to the main thread
 *  synchronously (meaning the worker thread is blocked until the call
 *  returns).  Each proxied call spins up a sync queue, which captures any
 *  synchronously dispatched events and ensures that they run synchronously
 *  on the worker as well.  Asynchronously dispatched events are posted to the
 *  worker thread to run asynchronously.  Some of the XHR state is mirrored on
 *  the worker thread to avoid needing a cross-thread call on every property
 *  access.
 *
 *  The XHR private is stored in the private slot of the XHR JSObject on the
 *  worker thread.  It is destroyed when that JSObject is GCd.  The private
 *  roots its JSObject while network activity is in progress.  It also
 *  adds itself as a feature to the worker to give itself a chance to clean up
 *  if the worker goes away during an XHR call.  It is important that the
 *  rooting and feature registration (collectively called pinning) happens at
 *  the proper times.  If we pin for too long we can cause memory leaks or even
 *  shutdown hangs.  If we don't pin for long enough we introduce a GC hazard.
 *
 *  The XHR is pinned from the time Send is called to roughly the time loadend
 *  is received.  There are some complications involved with Abort and XHR
 *  reuse.  We maintain a counter on the main thread of how many times Send was
 *  called on this XHR, and we decrement the counter every time we receive a
 *  loadend event.  When the counter reaches zero we dispatch a runnable to the
 *  worker thread to unpin the XHR.  We only decrement the counter if the 
 *  dispatch was successful, because the worker may no longer be accepting
 *  regular runnables.  In the event that we reach Proxy::Teardown and there
 *  the outstanding Send count is still non-zero, we dispatch a control
 *  runnable which is guaranteed to run.
 *
 *  NB: Some of this could probably be simplified now that we have the
 *  inner/outer channel ids.
 */

class Proxy final : public nsIDOMEventListener
{
public:
  // Read on multiple threads.
  WorkerPrivate* mWorkerPrivate;
  XMLHttpRequestWorker* mXMLHttpRequestPrivate;

  // XHR Params:
  bool mMozAnon;
  bool mMozSystem;

  // Only touched on the main thread.
  RefPtr<XMLHttpRequestMainThread> mXHR;
  nsCOMPtr<nsIXMLHttpRequestUpload> mXHRUpload;
  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
  nsCOMPtr<nsIEventTarget> mSyncEventResponseTarget;
  uint32_t mInnerEventStreamId;
  uint32_t mInnerChannelId;
  uint32_t mOutstandingSendCount;

  // Only touched on the worker thread.
  uint32_t mOuterEventStreamId;
  uint32_t mOuterChannelId;
  uint32_t mOpenCount;
  uint64_t mLastLoaded;
  uint64_t mLastTotal;
  uint64_t mLastUploadLoaded;
  uint64_t mLastUploadTotal;
  bool mIsSyncXHR;
  bool mLastLengthComputable;
  bool mLastUploadLengthComputable;
  bool mSeenLoadStart;
  bool mSeenUploadLoadStart;

  // Only touched on the main thread.
  bool mUploadEventListenersAttached;
  bool mMainThreadSeenLoadStart;
  bool mInOpen;
  bool mArrayBufferResponseWasTransferred;

public:
  Proxy(XMLHttpRequestWorker* aXHRPrivate, bool aMozAnon, bool aMozSystem)
  : mWorkerPrivate(nullptr), mXMLHttpRequestPrivate(aXHRPrivate),
    mMozAnon(aMozAnon), mMozSystem(aMozSystem),
    mInnerEventStreamId(0), mInnerChannelId(0), mOutstandingSendCount(0),
    mOuterEventStreamId(0), mOuterChannelId(0), mOpenCount(0), mLastLoaded(0),
    mLastTotal(0), mLastUploadLoaded(0), mLastUploadTotal(0), mIsSyncXHR(false),
    mLastLengthComputable(false), mLastUploadLengthComputable(false),
    mSeenLoadStart(false), mSeenUploadLoadStart(false),
    mUploadEventListenersAttached(false), mMainThreadSeenLoadStart(false),
    mInOpen(false), mArrayBufferResponseWasTransferred(false)
  { }

  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIDOMEVENTLISTENER

  bool
  Init();

  void
  Teardown(bool aSendUnpin);

  bool
  AddRemoveEventListeners(bool aUpload, bool aAdd);

  void
  Reset()
  {
    AssertIsOnMainThread();

    if (mUploadEventListenersAttached) {
      AddRemoveEventListeners(true, false);
    }
  }

  already_AddRefed<nsIEventTarget>
  GetEventTarget()
  {
    AssertIsOnMainThread();

    nsCOMPtr<nsIEventTarget> target = mSyncEventResponseTarget ?
                                      mSyncEventResponseTarget :
                                      mSyncLoopTarget;
    return target.forget();
  }

private:
  ~Proxy()
  {
    MOZ_ASSERT(!mXHR);
    MOZ_ASSERT(!mXHRUpload);
    MOZ_ASSERT(!mOutstandingSendCount);
  }
};

class WorkerThreadProxySyncRunnable : public WorkerMainThreadRunnable
{
protected:
  RefPtr<Proxy> mProxy;

private:
  // mErrorCode is set on the main thread by MainThreadRun and it's used to at
  // the end of the Dispatch() to return the error code.
  nsresult mErrorCode;

public:
  WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
  : WorkerMainThreadRunnable(aWorkerPrivate, NS_LITERAL_CSTRING("XHR"))
  , mProxy(aProxy)
  , mErrorCode(NS_OK)
  {
    MOZ_ASSERT(aWorkerPrivate);
    MOZ_ASSERT(aProxy);
    aWorkerPrivate->AssertIsOnWorkerThread();
  }

  void
  Dispatch(ErrorResult& aRv)
  {
    WorkerMainThreadRunnable::Dispatch(aRv);
    if (NS_WARN_IF(aRv.Failed())) {
      return;
    }

    if (NS_FAILED(mErrorCode)) {
      aRv.Throw(mErrorCode);
    }
  }

protected:
  virtual ~WorkerThreadProxySyncRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) = 0;

private:
  virtual bool MainThreadRun() override;
};

class SendRunnable final
  : public WorkerThreadProxySyncRunnable
  , public StructuredCloneHolder
{
  nsString mStringBody;
  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
  bool mHasUploadListeners;

public:
  SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
               const nsAString& aStringBody)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
  , StructuredCloneHolder(CloningSupported, TransferringNotSupported,
                          StructuredCloneScope::SameProcessDifferentThread)
  , mStringBody(aStringBody)
  , mHasUploadListeners(false)
  {
  }

  void SetHaveUploadListeners(bool aHasUploadListeners)
  {
    mHasUploadListeners = aHasUploadListeners;
  }

  void SetSyncLoopTarget(nsIEventTarget* aSyncLoopTarget)
  {
    mSyncLoopTarget = aSyncLoopTarget;
  }

private:
  ~SendRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override;
};

namespace {

enum
{
  STRING_abort = 0,
  STRING_error,
  STRING_load,
  STRING_loadstart,
  STRING_progress,
  STRING_timeout,
  STRING_readystatechange,
  STRING_loadend,

  STRING_COUNT,

  STRING_LAST_XHR = STRING_loadend,
  STRING_LAST_EVENTTARGET = STRING_timeout
};

static_assert(STRING_LAST_XHR >= STRING_LAST_EVENTTARGET, "Bad string setup!");
static_assert(STRING_LAST_XHR == STRING_COUNT - 1, "Bad string setup!");

const char* const sEventStrings[] = {
  // nsIXMLHttpRequestEventTarget event types, supported by both XHR and Upload.
  "abort",
  "error",
  "load",
  "loadstart",
  "progress",
  "timeout",

  // nsIXMLHttpRequest event types, supported only by XHR.
  "readystatechange",
  "loadend",
};

static_assert(MOZ_ARRAY_LENGTH(sEventStrings) == STRING_COUNT,
              "Bad string count!");

class MainThreadProxyRunnable : public MainThreadWorkerSyncRunnable
{
protected:
  RefPtr<Proxy> mProxy;

  MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
  : MainThreadWorkerSyncRunnable(aWorkerPrivate, aProxy->GetEventTarget()),
    mProxy(aProxy)
  {
    MOZ_ASSERT(aProxy);
  }

  virtual ~MainThreadProxyRunnable()
  { }
};

class XHRUnpinRunnable final : public MainThreadWorkerControlRunnable
{
  XMLHttpRequestWorker* mXMLHttpRequestPrivate;

public:
  XHRUnpinRunnable(WorkerPrivate* aWorkerPrivate,
                   XMLHttpRequestWorker* aXHRPrivate)
  : MainThreadWorkerControlRunnable(aWorkerPrivate),
    mXMLHttpRequestPrivate(aXHRPrivate)
  {
    MOZ_ASSERT(aXHRPrivate);
  }

private:
  ~XHRUnpinRunnable()
  { }

  bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
  {
    if (mXMLHttpRequestPrivate->SendInProgress()) {
      mXMLHttpRequestPrivate->Unpin();
    }

    return true;
  }
};

class AsyncTeardownRunnable final : public Runnable
{
  RefPtr<Proxy> mProxy;

public:
  explicit AsyncTeardownRunnable(Proxy* aProxy)
  : mProxy(aProxy)
  {
    MOZ_ASSERT(aProxy);
  }

private:
  ~AsyncTeardownRunnable()
  { }

  NS_IMETHOD
  Run() override
  {
    AssertIsOnMainThread();

    // This means the XHR was GC'd, so we can't be pinned, and we don't need to
    // try to unpin.
    mProxy->Teardown(/* aSendUnpin */ false);
    mProxy = nullptr;

    return NS_OK;
  }
};

class LoadStartDetectionRunnable final : public Runnable,
                                         public nsIDOMEventListener
{
  WorkerPrivate* mWorkerPrivate;
  RefPtr<Proxy> mProxy;
  RefPtr<XMLHttpRequest> mXHR;
  XMLHttpRequestWorker* mXMLHttpRequestPrivate;
  nsString mEventType;
  uint32_t mChannelId;
  bool mReceivedLoadStart;

  class ProxyCompleteRunnable final : public MainThreadProxyRunnable
  {
    XMLHttpRequestWorker* mXMLHttpRequestPrivate;
    uint32_t mChannelId;

  public:
    ProxyCompleteRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                          XMLHttpRequestWorker* aXHRPrivate, uint32_t aChannelId)
    : MainThreadProxyRunnable(aWorkerPrivate, aProxy),
      mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(aChannelId)
    { }

  private:
    ~ProxyCompleteRunnable()
    { }

    virtual bool
    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
    {
      if (mChannelId != mProxy->mOuterChannelId) {
        // Threads raced, this event is now obsolete.
        return true;
      }

      if (mSyncLoopTarget) {
        aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, true);
      }

      if (mXMLHttpRequestPrivate->SendInProgress()) {
        mXMLHttpRequestPrivate->Unpin();
      }

      return true;
    }

    nsresult
    Cancel() override
    {
      // This must run!
      nsresult rv = MainThreadProxyRunnable::Cancel();
      nsresult rv2 = Run();
      return NS_FAILED(rv) ? rv : rv2;
    }
  };

public:
  LoadStartDetectionRunnable(Proxy* aProxy, XMLHttpRequestWorker* aXHRPrivate)
  : mWorkerPrivate(aProxy->mWorkerPrivate), mProxy(aProxy), mXHR(aProxy->mXHR),
    mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(mProxy->mInnerChannelId),
    mReceivedLoadStart(false)
  {
    AssertIsOnMainThread();
    mEventType.AssignWithConversion(sEventStrings[STRING_loadstart]);
  }

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_NSIRUNNABLE
  NS_DECL_NSIDOMEVENTLISTENER

  bool
  RegisterAndDispatch()
  {
    AssertIsOnMainThread();

    if (NS_FAILED(mXHR->AddEventListener(mEventType, this, false, false, 2))) {
      NS_WARNING("Failed to add event listener!");
      return false;
    }

    return NS_SUCCEEDED(NS_DispatchToCurrentThread(this));
  }

private:
  ~LoadStartDetectionRunnable()
  {
    AssertIsOnMainThread();
    }
};

class EventRunnable final : public MainThreadProxyRunnable
                          , public StructuredCloneHolder
{
  nsString mType;
  nsString mResponseType;
  JS::Heap<JS::Value> mResponse;
  XMLHttpRequestStringSnapshot mResponseText;
  nsString mResponseURL;
  nsCString mStatusText;
  uint64_t mLoaded;
  uint64_t mTotal;
  uint32_t mEventStreamId;
  uint32_t mStatus;
  uint16_t mReadyState;
  bool mUploadEvent;
  bool mProgressEvent;
  bool mLengthComputable;
  bool mUseCachedArrayBufferResponse;
  nsresult mResponseTextResult;
  nsresult mStatusResult;
  nsresult mResponseResult;
  // mScopeObj is used in PreDispatch only.  We init it in our constructor, and
  // reset() in PreDispatch, to ensure that it's not still linked into the
  // runtime once we go off-thread.
  JS::PersistentRooted<JSObject*> mScopeObj;

public:
  EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
                bool aLengthComputable, uint64_t aLoaded, uint64_t aTotal,
                JS::Handle<JSObject*> aScopeObj)
  : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy),
    StructuredCloneHolder(CloningSupported, TransferringNotSupported,
                          StructuredCloneScope::SameProcessDifferentThread),
    mType(aType), mResponse(JS::UndefinedValue()), mLoaded(aLoaded),
    mTotal(aTotal), mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0),
    mReadyState(0), mUploadEvent(aUploadEvent), mProgressEvent(true),
    mLengthComputable(aLengthComputable), mUseCachedArrayBufferResponse(false),
    mResponseTextResult(NS_OK), mStatusResult(NS_OK), mResponseResult(NS_OK),
    mScopeObj(RootingCx(), aScopeObj)
  { }

  EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
                JS::Handle<JSObject*> aScopeObj)
  : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy),
    StructuredCloneHolder(CloningSupported, TransferringNotSupported,
                          StructuredCloneScope::SameProcessDifferentThread),
    mType(aType), mResponse(JS::UndefinedValue()), mLoaded(0), mTotal(0),
    mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0),
    mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0),
    mUseCachedArrayBufferResponse(false), mResponseTextResult(NS_OK),
    mStatusResult(NS_OK), mResponseResult(NS_OK),
    mScopeObj(RootingCx(), aScopeObj)
  { }

private:
  ~EventRunnable()
  { }

  virtual bool
  PreDispatch(WorkerPrivate* /* unused */) override final;

  virtual bool
  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
};

class SyncTeardownRunnable final : public WorkerThreadProxySyncRunnable
{
public:
  SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
  { }

private:
  ~SyncTeardownRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override
  {
    mProxy->Teardown(/* aSendUnpin */ true);
    MOZ_ASSERT(!mProxy->mSyncLoopTarget);
  }
};

class SetBackgroundRequestRunnable final :
  public WorkerThreadProxySyncRunnable
{
  bool mValue;

public:
  SetBackgroundRequestRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                               bool aValue)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
  , mValue(aValue)
  { }

private:
  ~SetBackgroundRequestRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override
  {
    mProxy->mXHR->SetMozBackgroundRequest(mValue, aRv);
  }
};

class SetWithCredentialsRunnable final :
  public WorkerThreadProxySyncRunnable
{
  bool mValue;

public:
  SetWithCredentialsRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                             bool aValue)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
  , mValue(aValue)
  { }

private:
  ~SetWithCredentialsRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override
  {
    mProxy->mXHR->SetWithCredentials(mValue, aRv);
  }
};

class SetResponseTypeRunnable final : public WorkerThreadProxySyncRunnable
{
  XMLHttpRequestResponseType mResponseType;

public:
  SetResponseTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                          XMLHttpRequestResponseType aResponseType)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
    mResponseType(aResponseType)
  { }

  XMLHttpRequestResponseType
  ResponseType()
  {
    return mResponseType;
  }

private:
  ~SetResponseTypeRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override
  {
    mProxy->mXHR->SetResponseType(mResponseType, aRv);
    if (!aRv.Failed()) {
      mResponseType = mProxy->mXHR->ResponseType();
    }
  }
};

class SetTimeoutRunnable final : public WorkerThreadProxySyncRunnable
{
  uint32_t mTimeout;

public:
  SetTimeoutRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                     uint32_t aTimeout)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
    mTimeout(aTimeout)
  { }

private:
  ~SetTimeoutRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override
  {
    mProxy->mXHR->SetTimeout(mTimeout, aRv);
  }
};

class AbortRunnable final : public WorkerThreadProxySyncRunnable
{
public:
  AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
  { }

private:
  ~AbortRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override;
};

class GetAllResponseHeadersRunnable final :
  public WorkerThreadProxySyncRunnable
{
  nsCString& mResponseHeaders;

public:
  GetAllResponseHeadersRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                                nsCString& aResponseHeaders)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
    mResponseHeaders(aResponseHeaders)
  { }

private:
  ~GetAllResponseHeadersRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override
  {
    mProxy->mXHR->GetAllResponseHeaders(mResponseHeaders, aRv);
  }
};

class GetResponseHeaderRunnable final : public WorkerThreadProxySyncRunnable
{
  const nsCString mHeader;
  nsCString& mValue;

public:
  GetResponseHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                            const nsACString& aHeader, nsCString& aValue)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
    mHeader(aHeader),
    mValue(aValue)
  { }

private:
  ~GetResponseHeaderRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override
  {
    mProxy->mXHR->GetResponseHeader(mHeader, mValue, aRv);
  }
};

class OpenRunnable final : public WorkerThreadProxySyncRunnable
{
  nsCString mMethod;
  nsString mURL;
  Optional<nsAString> mUser;
  nsString mUserStr;
  Optional<nsAString> mPassword;
  nsString mPasswordStr;
  bool mBackgroundRequest;
  bool mWithCredentials;
  uint32_t mTimeout;
  XMLHttpRequestResponseType mResponseType;

public:
  OpenRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
               const nsACString& aMethod, const nsAString& aURL,
               const Optional<nsAString>& aUser,
               const Optional<nsAString>& aPassword,
               bool aBackgroundRequest, bool aWithCredentials,
               uint32_t aTimeout, XMLHttpRequestResponseType aResponseType)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
    mMethod(aMethod),
    mURL(aURL), mBackgroundRequest(aBackgroundRequest),
    mWithCredentials(aWithCredentials), mTimeout(aTimeout),
    mResponseType(aResponseType)
  {
    if (aUser.WasPassed()) {
      mUserStr = aUser.Value();
      mUser = &mUserStr;
    }
    if (aPassword.WasPassed()) {
      mPasswordStr = aPassword.Value();
      mPassword = &mPasswordStr;
    }
  }

private:
  ~OpenRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override
  {
    WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
    mProxy->mWorkerPrivate = mWorkerPrivate;

    aRv = MainThreadRunInternal();

    mProxy->mWorkerPrivate = oldWorker;
  }

  nsresult
  MainThreadRunInternal();
};

class SetRequestHeaderRunnable final : public WorkerThreadProxySyncRunnable
{
  nsCString mHeader;
  nsCString mValue;

public:
  SetRequestHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                           const nsACString& aHeader, const nsACString& aValue)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
    mHeader(aHeader),
    mValue(aValue)
  { }

private:
  ~SetRequestHeaderRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override
  {
    mProxy->mXHR->SetRequestHeader(mHeader, mValue, aRv);
  }
};

class OverrideMimeTypeRunnable final : public WorkerThreadProxySyncRunnable
{
  nsString mMimeType;

public:
  OverrideMimeTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                           const nsAString& aMimeType)
  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
    mMimeType(aMimeType)
  { }

private:
  ~OverrideMimeTypeRunnable()
  { }

  virtual void
  RunOnMainThread(ErrorResult& aRv) override
  {
    mProxy->mXHR->OverrideMimeType(mMimeType, aRv);
  }
};

class AutoUnpinXHR
{
  XMLHttpRequestWorker* mXMLHttpRequestPrivate;

public:
  explicit AutoUnpinXHR(XMLHttpRequestWorker* aXMLHttpRequestPrivate)
  : mXMLHttpRequestPrivate(aXMLHttpRequestPrivate)
  {
    MOZ_ASSERT(aXMLHttpRequestPrivate);
  }

  ~AutoUnpinXHR()
  {
    if (mXMLHttpRequestPrivate) {
      mXMLHttpRequestPrivate->Unpin();
    }
  }

  void Clear()
  {
    mXMLHttpRequestPrivate = nullptr;
  }
};

} // namespace

bool
Proxy::Init()
{
  AssertIsOnMainThread();
  MOZ_ASSERT(mWorkerPrivate);

  if (mXHR) {
    return true;
  }

  nsPIDOMWindowInner* ownerWindow = mWorkerPrivate->GetWindow();
  if (ownerWindow && !ownerWindow->IsCurrentInnerWindow()) {
    NS_WARNING("Window has navigated, cannot create XHR here.");
    return false;
  }

  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(ownerWindow);

  mXHR = new XMLHttpRequestMainThread();
  mXHR->Construct(mWorkerPrivate->GetPrincipal(), global,
                  mWorkerPrivate->GetBaseURI(),
                  mWorkerPrivate->GetLoadGroup());

  mXHR->SetParameters(mMozAnon, mMozSystem);

  ErrorResult rv;
  mXHRUpload = mXHR->GetUpload(rv);
  if (NS_WARN_IF(rv.Failed())) {
    mXHR = nullptr;
    return false;
  }

  if (!AddRemoveEventListeners(false, true)) {
    mXHR = nullptr;
    mXHRUpload = nullptr;
    return false;
  }

  return true;
}

void
Proxy::Teardown(bool aSendUnpin)
{
  AssertIsOnMainThread();

  if (mXHR) {
    Reset();

    // NB: We are intentionally dropping events coming from xhr.abort on the
    // floor.
    AddRemoveEventListeners(false, false);

    ErrorResult rv;
    mXHR->Abort(rv);
    if (NS_WARN_IF(rv.Failed())) {
      rv.SuppressException();
    }

    if (mOutstandingSendCount) {
      if (aSendUnpin) {
        RefPtr<XHRUnpinRunnable> runnable =
          new XHRUnpinRunnable(mWorkerPrivate, mXMLHttpRequestPrivate);
        if (!runnable->Dispatch()) {
          NS_RUNTIMEABORT("We're going to hang at shutdown anyways.");
        }
      }

      if (mSyncLoopTarget) {
        // We have an unclosed sync loop.  Fix that now.
        RefPtr<MainThreadStopSyncLoopRunnable> runnable =
          new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
                                             mSyncLoopTarget.forget(),
                                             false);
        if (!runnable->Dispatch()) {
          NS_RUNTIMEABORT("We're going to hang at shutdown anyways.");
        }
      }

      mOutstandingSendCount = 0;
    }

    mWorkerPrivate = nullptr;
    mXHRUpload = nullptr;
    mXHR = nullptr;
  }

  MOZ_ASSERT(!mWorkerPrivate);
  MOZ_ASSERT(!mSyncLoopTarget);
}

bool
Proxy::AddRemoveEventListeners(bool aUpload, bool aAdd)
{
  AssertIsOnMainThread();

  NS_ASSERTION(!aUpload ||
               (mUploadEventListenersAttached && !aAdd) ||
               (!mUploadEventListenersAttached && aAdd),
               "Messed up logic for upload listeners!");

  nsCOMPtr<nsIDOMEventTarget> target =
    aUpload ?
    do_QueryInterface(mXHRUpload) :
    do_QueryInterface(static_cast<nsIXMLHttpRequest*>(mXHR.get()));
  NS_ASSERTION(target, "This should never fail!");

  uint32_t lastEventType = aUpload ? STRING_LAST_EVENTTARGET : STRING_LAST_XHR;

  nsAutoString eventType;
  for (uint32_t index = 0; index <= lastEventType; index++) {
    eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]);
    if (aAdd) {
      if (NS_FAILED(target->AddEventListener(eventType, this, false))) {
        return false;
      }
    }
    else if (NS_FAILED(target->RemoveEventListener(eventType, this, false))) {
      return false;
    }
  }

  if (aUpload) {
    mUploadEventListenersAttached = aAdd;
  }

  return true;
}

NS_IMPL_ISUPPORTS(Proxy, nsIDOMEventListener)

NS_IMETHODIMP
Proxy::HandleEvent(nsIDOMEvent* aEvent)
{
  AssertIsOnMainThread();

  if (!mWorkerPrivate || !mXMLHttpRequestPrivate) {
    NS_ERROR("Shouldn't get here!");
    return NS_OK;
  }

  nsString type;
  if (NS_FAILED(aEvent->GetType(type))) {
    NS_WARNING("Failed to get event type!");
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIDOMEventTarget> target;
  if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target)))) {
    NS_WARNING("Failed to get target!");
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIXMLHttpRequestUpload> uploadTarget = do_QueryInterface(target);
  ProgressEvent* progressEvent = aEvent->InternalDOMEvent()->AsProgressEvent();

  if (mInOpen && type.EqualsASCII(sEventStrings[STRING_readystatechange])) {
    uint16_t readyState = 0;
    if (NS_SUCCEEDED(mXHR->GetReadyState(&readyState)) &&
        readyState == nsIXMLHttpRequest::OPENED) {
      mInnerEventStreamId++;
    }
  }

  {
    AutoSafeJSContext cx;
    JSAutoRequest ar(cx);

    JS::Rooted<JS::Value> value(cx);
    if (!GetOrCreateDOMReflectorNoWrap(cx, mXHR, &value)) {
      return NS_ERROR_FAILURE;
    }

    JS::Rooted<JSObject*> scope(cx, &value.toObject());

    RefPtr<EventRunnable> runnable;
    if (progressEvent) {
      runnable = new EventRunnable(this, !!uploadTarget, type,
                                   progressEvent->LengthComputable(),
                                   progressEvent->Loaded(),
                                   progressEvent->Total(),
                                   scope);
    }
    else {
      runnable = new EventRunnable(this, !!uploadTarget, type, scope);
    }

    runnable->Dispatch();
  }

  if (!uploadTarget) {
    if (type.EqualsASCII(sEventStrings[STRING_loadstart])) {
      mMainThreadSeenLoadStart = true;
    }
    else if (mMainThreadSeenLoadStart &&
             type.EqualsASCII(sEventStrings[STRING_loadend])) {
      mMainThreadSeenLoadStart = false;

      RefPtr<LoadStartDetectionRunnable> runnable =
        new LoadStartDetectionRunnable(this, mXMLHttpRequestPrivate);
      if (!runnable->RegisterAndDispatch()) {
        NS_WARNING("Failed to dispatch LoadStartDetectionRunnable!");
      }
    }
  }

  return NS_OK;
}

NS_IMPL_ISUPPORTS_INHERITED(LoadStartDetectionRunnable, Runnable,
                                                        nsIDOMEventListener)

NS_IMETHODIMP
LoadStartDetectionRunnable::Run()
{
  AssertIsOnMainThread();

  if (NS_FAILED(mXHR->RemoveEventListener(mEventType, this, false))) {
    NS_WARNING("Failed to remove event listener!");
  }

  if (!mReceivedLoadStart) {
    if (mProxy->mOutstandingSendCount > 1) {
      mProxy->mOutstandingSendCount--;
    } else if (mProxy->mOutstandingSendCount == 1) {
      mProxy->Reset();

      RefPtr<ProxyCompleteRunnable> runnable =
        new ProxyCompleteRunnable(mWorkerPrivate, mProxy,
                                  mXMLHttpRequestPrivate, mChannelId);
      if (runnable->Dispatch()) {
        mProxy->mWorkerPrivate = nullptr;
        mProxy->mSyncLoopTarget = nullptr;
        mProxy->mOutstandingSendCount--;
      }
    }
  }

  mProxy = nullptr;
  mXHR = nullptr;
  mXMLHttpRequestPrivate = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
LoadStartDetectionRunnable::HandleEvent(nsIDOMEvent* aEvent)
{
  AssertIsOnMainThread();

#ifdef DEBUG
  {
    nsString type;
    if (NS_SUCCEEDED(aEvent->GetType(type))) {
      MOZ_ASSERT(type == mEventType);
    }
    else {
      NS_WARNING("Failed to get event type!");
    }
  }
#endif

  mReceivedLoadStart = true;
  return NS_OK;
}

bool
EventRunnable::PreDispatch(WorkerPrivate* /* unused */)
{
  AssertIsOnMainThread();

  AutoJSAPI jsapi;
  DebugOnly<bool> ok = jsapi.Init(xpc::NativeGlobal(mScopeObj));
  MOZ_ASSERT(ok);
  JSContext* cx = jsapi.cx();
  // Now keep the mScopeObj alive for the duration
  JS::Rooted<JSObject*> scopeObj(cx, mScopeObj);
  // And reset mScopeObj now, before we have a chance to run its destructor on
  // some background thread.
  mScopeObj.reset();

  RefPtr<XMLHttpRequestMainThread>& xhr = mProxy->mXHR;
  MOZ_ASSERT(xhr);

  if (NS_FAILED(xhr->GetResponseType(mResponseType))) {
    MOZ_ASSERT(false, "This should never fail!");
  }

  ErrorResult rv;
  xhr->GetResponseText(mResponseText, rv);
  mResponseTextResult = rv.StealNSResult();

  if (NS_SUCCEEDED(mResponseTextResult)) {
    mResponseResult = mResponseTextResult;
    if (mResponseText.IsVoid()) {
      mResponse.setNull();
    }
  }
  else {
    JS::Rooted<JS::Value> response(cx);
    mResponseResult = xhr->GetResponse(cx, &response);
    if (NS_SUCCEEDED(mResponseResult)) {
      if (!response.isGCThing()) {
        mResponse = response;
      } else {
        bool doClone = true;
        JS::Rooted<JS::Value> transferable(cx);
        JS::Rooted<JSObject*> obj(cx, response.isObjectOrNull() ?
                                  response.toObjectOrNull() : nullptr);
        if (obj && JS_IsArrayBufferObject(obj)) {
          // Use cached response if the arraybuffer has been transfered.
          if (mProxy->mArrayBufferResponseWasTransferred) {
            MOZ_ASSERT(JS_IsDetachedArrayBufferObject(obj));
            mUseCachedArrayBufferResponse = true;
            doClone = false;
          } else {
            MOZ_ASSERT(!JS_IsDetachedArrayBufferObject(obj));
            JS::AutoValueArray<1> argv(cx);
            argv[0].set(response);
            obj = JS_NewArrayObject(cx, argv);
            if (obj) {
              transferable.setObject(*obj);
              // Only cache the response when the readyState is DONE.
              if (xhr->ReadyState() == nsIXMLHttpRequest::DONE) {
                mProxy->mArrayBufferResponseWasTransferred = true;
              }
            } else {
              mResponseResult = NS_ERROR_OUT_OF_MEMORY;
              doClone = false;
            }
          }
        }

        if (doClone) {
          Write(cx, response, transferable, JS::CloneDataPolicy(), rv);
          if (NS_WARN_IF(rv.Failed())) {
            NS_WARNING("Failed to clone response!");
            mResponseResult = rv.StealNSResult();
            mProxy->mArrayBufferResponseWasTransferred = false;
          }
        }
      }
    }
  }

  mStatusResult = xhr->GetStatus(&mStatus);

  xhr->GetStatusText(mStatusText, rv);
  MOZ_ASSERT(!rv.Failed());

  mReadyState = xhr->ReadyState();

  xhr->GetResponseURL(mResponseURL);

  return true;
}

bool
EventRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
  if (mEventStreamId != mProxy->mOuterEventStreamId) {
    // Threads raced, this event is now obsolete.
    return true;
  }

  if (!mProxy->mXMLHttpRequestPrivate) {
    // Object was finalized, bail.
    return true;
  }

  if (mType.EqualsASCII(sEventStrings[STRING_loadstart])) {
    if (mUploadEvent) {
      mProxy->mSeenUploadLoadStart = true;
    }
    else {
      mProxy->mSeenLoadStart = true;
    }
  }
  else if (mType.EqualsASCII(sEventStrings[STRING_loadend])) {
    if (mUploadEvent) {
      mProxy->mSeenUploadLoadStart = false;
    }
    else {
      if (!mProxy->mSeenLoadStart) {
        // We've already dispatched premature abort events.
        return true;
      }
      mProxy->mSeenLoadStart = false;
    }
  }
  else if (mType.EqualsASCII(sEventStrings[STRING_abort])) {
    if ((mUploadEvent && !mProxy->mSeenUploadLoadStart) ||
        (!mUploadEvent && !mProxy->mSeenLoadStart)) {
      // We've already dispatched premature abort events.
      return true;
    }
  }

  if (mProgressEvent) {
    // Cache these for premature abort events.
    if (mUploadEvent) {
      mProxy->mLastUploadLengthComputable = mLengthComputable;
      mProxy->mLastUploadLoaded = mLoaded;
      mProxy->mLastUploadTotal = mTotal;
    }
    else {
      mProxy->mLastLengthComputable = mLengthComputable;
      mProxy->mLastLoaded = mLoaded;
      mProxy->mLastTotal = mTotal;
    }
  }

  JS::Rooted<UniquePtr<XMLHttpRequestWorker::StateData>> state(aCx, new XMLHttpRequestWorker::StateData());

  state->mResponseTextResult = mResponseTextResult;

  state->mResponseText = mResponseText;

  if (NS_SUCCEEDED(mResponseTextResult)) {
    MOZ_ASSERT(mResponse.isUndefined() || mResponse.isNull());
    state->mResponseResult = mResponseTextResult;
    state->mResponse = mResponse;
  }
  else {
    state->mResponseResult = mResponseResult;

    if (NS_SUCCEEDED(mResponseResult)) {
      if (HasData()) {
        MOZ_ASSERT(mResponse.isUndefined());

        ErrorResult rv;
        JS::Rooted<JS::Value> response(aCx);

        GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
        nsCOMPtr<nsIGlobalObject> global =
          do_QueryInterface(globalObj.GetAsSupports());

        Read(global, aCx, &response, rv);
        if (NS_WARN_IF(rv.Failed())) {
          rv.SuppressException();
          return false;
        }

        state->mResponse = response;
      }
      else {
        state->mResponse = mResponse;
      }
    }
  }

  state->mStatusResult = mStatusResult;
  state->mStatus = mStatus;

  state->mStatusText = mStatusText;

  state->mReadyState = mReadyState;

  state->mResponseURL = mResponseURL;

  XMLHttpRequestWorker* xhr = mProxy->mXMLHttpRequestPrivate;
  xhr->UpdateState(*state.get(), mUseCachedArrayBufferResponse);

  if (mType.EqualsASCII(sEventStrings[STRING_readystatechange])) {
    if (mReadyState == 4 && !mUploadEvent && !mProxy->mSeenLoadStart) {
      // We've already dispatched premature abort events.
      return true;
    }
  }

  if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) {
    return true;
  }

  JS::Rooted<JSString*> type(aCx,
    JS_NewUCStringCopyN(aCx, mType.get(), mType.Length()));
  if (!type) {
    return false;
  }

  XMLHttpRequestEventTarget* target;
  if (mUploadEvent) {
    target = xhr->GetUploadObjectNoCreate();
  }
  else {
    target = xhr;
  }

  MOZ_ASSERT(target);

  RefPtr<Event> event;
  if (mProgressEvent) {
    ProgressEventInit init;
    init.mBubbles = false;
    init.mCancelable = false;
    init.mLengthComputable = mLengthComputable;
    init.mLoaded = mLoaded;
    init.mTotal = mTotal;

    event = ProgressEvent::Constructor(target, mType, init);
  }
  else {
    event = NS_NewDOMEvent(target, nullptr, nullptr);

    if (event) {
      event->InitEvent(mType, false, false);
    }
  }

  if (!event) {
    return false;
  }

  event->SetTrusted(true);

  target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);

  // After firing the event set mResponse to JSVAL_NULL for chunked response
  // types.
  if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) {
    xhr->NullResponseText();
  }

  return true;
}

bool
WorkerThreadProxySyncRunnable::MainThreadRun()
{
  AssertIsOnMainThread();

  nsCOMPtr<nsIEventTarget> tempTarget = mSyncLoopTarget;

  mProxy->mSyncEventResponseTarget.swap(tempTarget);

  ErrorResult rv;
  RunOnMainThread(rv);
  mErrorCode = rv.StealNSResult();

  mProxy->mSyncEventResponseTarget.swap(tempTarget);

  return true;
}

void
AbortRunnable::RunOnMainThread(ErrorResult& aRv)
{
  mProxy->mInnerEventStreamId++;

  WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
  mProxy->mWorkerPrivate = mWorkerPrivate;

  mProxy->mXHR->Abort(aRv);

  mProxy->mWorkerPrivate = oldWorker;

  mProxy->Reset();
}

nsresult
OpenRunnable::MainThreadRunInternal()
{
  if (!mProxy->Init()) {
    return NS_ERROR_DOM_INVALID_STATE_ERR;
  }

  nsresult rv;

  if (mBackgroundRequest) {
    rv = mProxy->mXHR->SetMozBackgroundRequest(mBackgroundRequest);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (mWithCredentials) {
    rv = mProxy->mXHR->SetWithCredentials(mWithCredentials);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (mTimeout) {
    rv = mProxy->mXHR->SetTimeout(mTimeout);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  MOZ_ASSERT(!mProxy->mInOpen);
  mProxy->mInOpen = true;

  ErrorResult rv2;
  mProxy->mXHR->Open(mMethod, mURL, true,
                     mUser.WasPassed() ? mUser.Value() : NullString(),
                     mPassword.WasPassed() ? mPassword.Value() : NullString(),
                     rv2);

  MOZ_ASSERT(mProxy->mInOpen);
  mProxy->mInOpen = false;

  if (rv2.Failed()) {
    return rv2.StealNSResult();
  }

  mProxy->mXHR->SetResponseType(mResponseType, rv2);
  if (rv2.Failed()) {
    return rv2.StealNSResult();
  }

  return NS_OK;
}

void
SendRunnable::RunOnMainThread(ErrorResult& aRv)
{
  nsCOMPtr<nsIVariant> variant;

  if (HasData()) {
    AutoSafeJSContext cx;
    JSAutoRequest ar(cx);

    nsIXPConnect* xpc = nsContentUtils::XPConnect();
    MOZ_ASSERT(xpc);

    JS::Rooted<JSObject*> globalObject(cx, JS::CurrentGlobalOrNull(cx));
    if (NS_WARN_IF(!globalObject)) {
      aRv.Throw(NS_ERROR_FAILURE);
      return;
    }

    nsCOMPtr<nsIGlobalObject> parent = xpc::NativeGlobal(globalObject);
    if (NS_WARN_IF(!parent)) {
      aRv.Throw(NS_ERROR_FAILURE);
      return;
    }

    JS::Rooted<JS::Value> body(cx);
    Read(parent, cx, &body, aRv);
    if (NS_WARN_IF(aRv.Failed())) {
      return;
    }

    aRv = xpc->JSValToVariant(cx, body, getter_AddRefs(variant));
    if (NS_WARN_IF(aRv.Failed())) {
      return;
    }
  }
  else {
    RefPtr<nsVariant> wvariant = new nsVariant();

    if (NS_FAILED(wvariant->SetAsAString(mStringBody))) {
      MOZ_ASSERT(false, "This should never fail!");
    }

    variant = wvariant;
  }

  // Send() has been already called, reset the proxy.
  if (mProxy->mWorkerPrivate) {
    mProxy->Reset();
  }

  mProxy->mWorkerPrivate = mWorkerPrivate;

  MOZ_ASSERT(!mProxy->mSyncLoopTarget);
  mProxy->mSyncLoopTarget.swap(mSyncLoopTarget);

  if (mHasUploadListeners) {
    // Send() can be called more than once before failure,
    // so don't attach the upload listeners more than once.
    if (!mProxy->mUploadEventListenersAttached &&
        !mProxy->AddRemoveEventListeners(true, true)) {
      MOZ_ASSERT(false, "This should never fail!");
    }
  }

  mProxy->mArrayBufferResponseWasTransferred = false;

  mProxy->mInnerChannelId++;

  aRv = mProxy->mXHR->Send(variant);

  if (!aRv.Failed()) {
    mProxy->mOutstandingSendCount++;

    if (!mHasUploadListeners) {
      // Send() can be called more than once before failure,
      // so don't attach the upload listeners more than once.
      if (!mProxy->mUploadEventListenersAttached &&
          !mProxy->AddRemoveEventListeners(true, true)) {
        MOZ_ASSERT(false, "This should never fail!");
      }
    }
  }
}

XMLHttpRequestWorker::XMLHttpRequestWorker(WorkerPrivate* aWorkerPrivate)
: mWorkerPrivate(aWorkerPrivate),
  mResponseType(XMLHttpRequestResponseType::Text), mTimeout(0),
  mRooted(false), mBackgroundRequest(false), mWithCredentials(false),
  mCanceled(false), mMozAnon(false), mMozSystem(false)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  mozilla::HoldJSObjects(this);
}

XMLHttpRequestWorker::~XMLHttpRequestWorker()
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  ReleaseProxy(XHRIsGoingAway);

  MOZ_ASSERT(!mRooted);

  mozilla::DropJSObjects(this);
}

NS_IMPL_ADDREF_INHERITED(XMLHttpRequestWorker, XMLHttpRequestEventTarget)
NS_IMPL_RELEASE_INHERITED(XMLHttpRequestWorker, XMLHttpRequestEventTarget)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XMLHttpRequestWorker)
NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)

NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestWorker)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestWorker,
                                                  XMLHttpRequestEventTarget)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestWorker,
                                                XMLHttpRequestEventTarget)
  tmp->ReleaseProxy(XHRIsGoingAway);
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
  tmp->mStateData.mResponse.setUndefined();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestWorker,
                                               XMLHttpRequestEventTarget)
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mStateData.mResponse)
NS_IMPL_CYCLE_COLLECTION_TRACE_END

/* static */ already_AddRefed<XMLHttpRequest>
XMLHttpRequestWorker::Construct(const GlobalObject& aGlobal,
                                const MozXMLHttpRequestParameters& aParams,
                                ErrorResult& aRv)
{
  JSContext* cx = aGlobal.Context();
  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
  MOZ_ASSERT(workerPrivate);

  Telemetry::Accumulate(Telemetry::XHR_IN_WORKER, 1);

  RefPtr<XMLHttpRequestWorker> xhr = new XMLHttpRequestWorker(workerPrivate);

  if (workerPrivate->XHRParamsAllowed()) {
    if (aParams.mMozSystem)
      xhr->mMozAnon = true;
    else
      xhr->mMozAnon = aParams.mMozAnon;
    xhr->mMozSystem = aParams.mMozSystem;
  }

  return xhr.forget();
}

void
XMLHttpRequestWorker::ReleaseProxy(ReleaseType aType)
{
  // Can't assert that we're on the worker thread here because mWorkerPrivate
  // may be gone.

  if (mProxy) {
    if (aType == XHRIsGoingAway) {
      // We're in a GC finalizer, so we can't do a sync call here (and we don't
      // need to).
      RefPtr<AsyncTeardownRunnable> runnable =
        new AsyncTeardownRunnable(mProxy);
      mProxy = nullptr;

      if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) {
        NS_ERROR("Failed to dispatch teardown runnable!");
      }
    } else {
      // This isn't necessary if the worker is going away or the XHR is going
      // away.
      if (aType == Default) {
        // Don't let any more events run.
        mProxy->mOuterEventStreamId++;
      }

      // We need to make a sync call here.
      RefPtr<SyncTeardownRunnable> runnable =
        new SyncTeardownRunnable(mWorkerPrivate, mProxy);
      mProxy = nullptr;

      ErrorResult forAssertionsOnly;
      runnable->Dispatch(forAssertionsOnly);
      if (forAssertionsOnly.Failed()) {
        NS_ERROR("Failed to dispatch teardown runnable!");
      }
    }
  }
}

void
XMLHttpRequestWorker::MaybePin(ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mRooted) {
    return;
  }

  if (!HoldWorker(mWorkerPrivate, Canceling)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  NS_ADDREF_THIS();

  mRooted = true;
}

void
XMLHttpRequestWorker::MaybeDispatchPrematureAbortEvents(ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();
  MOZ_ASSERT(mProxy);

  // Only send readystatechange event when state changed.
  bool isStateChanged = false;
  if (mStateData.mReadyState != 4) {
    isStateChanged = true;
    mStateData.mReadyState = 4;
  }

  if (mProxy->mSeenUploadLoadStart) {
    MOZ_ASSERT(mUpload);

    DispatchPrematureAbortEvent(mUpload, NS_LITERAL_STRING("abort"), true,
                                aRv);
    if (aRv.Failed()) {
      return;
    }

    DispatchPrematureAbortEvent(mUpload, NS_LITERAL_STRING("loadend"), true,
                                aRv);
    if (aRv.Failed()) {
      return;
    }

    mProxy->mSeenUploadLoadStart = false;
  }

  if (mProxy->mSeenLoadStart) {
    if (isStateChanged) {
      DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("readystatechange"),
                                  false, aRv);
      if (aRv.Failed()) {
        return;
      }
    }

    DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("abort"), false, aRv);
    if (aRv.Failed()) {
      return;
    }

    DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("loadend"), false,
                                aRv);
    if (aRv.Failed()) {
      return;
    }

    mProxy->mSeenLoadStart = false;
  }
}

void
XMLHttpRequestWorker::DispatchPrematureAbortEvent(EventTarget* aTarget,
                                                  const nsAString& aEventType,
                                                  bool aUploadTarget,
                                                  ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();
  MOZ_ASSERT(aTarget);

  if (!mProxy) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  RefPtr<Event> event;
  if (aEventType.EqualsLiteral("readystatechange")) {
    event = NS_NewDOMEvent(aTarget, nullptr, nullptr);
    event->InitEvent(aEventType, false, false);
  }
  else {
    ProgressEventInit init;
    init.mBubbles = false;
    init.mCancelable = false;
    if (aUploadTarget) {
      init.mLengthComputable = mProxy->mLastUploadLengthComputable;
      init.mLoaded = mProxy->mLastUploadLoaded;
      init.mTotal = mProxy->mLastUploadTotal;
    }
    else {
      init.mLengthComputable = mProxy->mLastLengthComputable;
      init.mLoaded = mProxy->mLastLoaded;
      init.mTotal = mProxy->mLastTotal;
    }
    event = ProgressEvent::Constructor(aTarget, aEventType, init);
  }

  if (!event) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  event->SetTrusted(true);

  aTarget->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
}

void
XMLHttpRequestWorker::Unpin()
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  MOZ_ASSERT(mRooted, "Mismatched calls to Unpin!");

  ReleaseWorker();

  mRooted = false;

  NS_RELEASE_THIS();
}

void
XMLHttpRequestWorker::SendInternal(SendRunnable* aRunnable,
                                   ErrorResult& aRv)
{
  MOZ_ASSERT(aRunnable);
  mWorkerPrivate->AssertIsOnWorkerThread();

  // No send() calls when open is running.
  if (mProxy->mOpenCount) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  bool hasUploadListeners = mUpload ? mUpload->HasListeners() : false;

  MaybePin(aRv);
  if (aRv.Failed()) {
    return;
  }

  AutoUnpinXHR autoUnpin(this);
  Maybe<AutoSyncLoopHolder> autoSyncLoop;

  nsCOMPtr<nsIEventTarget> syncLoopTarget;
  bool isSyncXHR = mProxy->mIsSyncXHR;
  if (isSyncXHR) {
    autoSyncLoop.emplace(mWorkerPrivate);
    syncLoopTarget = autoSyncLoop->EventTarget();
  }

  mProxy->mOuterChannelId++;

  aRunnable->SetSyncLoopTarget(syncLoopTarget);
  aRunnable->SetHaveUploadListeners(hasUploadListeners);

  aRunnable->Dispatch(aRv);
  if (aRv.Failed()) {
    // Dispatch() may have spun the event loop and we may have already unrooted.
    // If so we don't want autoUnpin to try again.
    if (!mRooted) {
      autoUnpin.Clear();
    }
    return;
  }

  if (!isSyncXHR)  {
    autoUnpin.Clear();
    MOZ_ASSERT(!autoSyncLoop);
    return;
  }

  autoUnpin.Clear();

  // Don't clobber an existing exception that we may have thrown on aRv
  // already... though can there really be one?  In any case, it seems to me
  // that this autoSyncLoop->Run() can never fail, since the StopSyncLoop call
  // for it will come from ProxyCompleteRunnable and that always passes true for
  // the second arg.
  if (!autoSyncLoop->Run() && !aRv.Failed()) {
    aRv.Throw(NS_ERROR_FAILURE);
  }
}

bool
XMLHttpRequestWorker::Notify(Status aStatus)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (aStatus >= Canceling && !mCanceled) {
    mCanceled = true;
    ReleaseProxy(WorkerIsGoingAway);
  }

  return true;
}

void
XMLHttpRequestWorker::Open(const nsACString& aMethod,
                           const nsAString& aUrl, bool aAsync,
                           const Optional<nsAString>& aUser,
                           const Optional<nsAString>& aPassword,
                           ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  if (mProxy) {
    MaybeDispatchPrematureAbortEvents(aRv);
    if (aRv.Failed()) {
      return;
    }
  }
  else {
    mProxy = new Proxy(this, mMozAnon, mMozSystem);
  }

  mProxy->mOuterEventStreamId++;

  RefPtr<OpenRunnable> runnable =
    new OpenRunnable(mWorkerPrivate, mProxy, aMethod, aUrl, aUser, aPassword,
                     mBackgroundRequest, mWithCredentials,
                     mTimeout, mResponseType);

  ++mProxy->mOpenCount;
  runnable->Dispatch(aRv);
  if (aRv.Failed()) {
    if (mProxy && !--mProxy->mOpenCount) {
      ReleaseProxy();
    }

    return;
  }

  // We have been released in one of the nested Open() calls.
  if (!mProxy) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  --mProxy->mOpenCount;
  mProxy->mIsSyncXHR = !aAsync;
}

void
XMLHttpRequestWorker::SetRequestHeader(const nsACString& aHeader,
                                       const nsACString& aValue, ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  if (!mProxy) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  RefPtr<SetRequestHeaderRunnable> runnable =
    new SetRequestHeaderRunnable(mWorkerPrivate, mProxy, aHeader, aValue);
  runnable->Dispatch(aRv);
}

void
XMLHttpRequestWorker::SetTimeout(uint32_t aTimeout, ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  mTimeout = aTimeout;

  if (!mProxy) {
    // Open may not have been called yet, in which case we'll handle the
    // timeout in OpenRunnable.
    return;
  }

  RefPtr<SetTimeoutRunnable> runnable =
    new SetTimeoutRunnable(mWorkerPrivate, mProxy, aTimeout);
  runnable->Dispatch(aRv);
}

void
XMLHttpRequestWorker::SetWithCredentials(bool aWithCredentials, ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  mWithCredentials = aWithCredentials;

  if (!mProxy) {
    // Open may not have been called yet, in which case we'll handle the
    // credentials in OpenRunnable.
    return;
  }

  RefPtr<SetWithCredentialsRunnable> runnable =
    new SetWithCredentialsRunnable(mWorkerPrivate, mProxy, aWithCredentials);
  runnable->Dispatch(aRv);
}

void
XMLHttpRequestWorker::SetMozBackgroundRequest(bool aBackgroundRequest,
                                              ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  mBackgroundRequest = aBackgroundRequest;

  if (!mProxy) {
    // Open may not have been called yet, in which case we'll handle the
    // background request in OpenRunnable.
    return;
  }

  RefPtr<SetBackgroundRequestRunnable> runnable =
    new SetBackgroundRequestRunnable(mWorkerPrivate, mProxy,
                                     aBackgroundRequest);
  runnable->Dispatch(aRv);
}

XMLHttpRequestUpload*
XMLHttpRequestWorker::GetUpload(ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return nullptr;
  }

  if (!mUpload) {
    mUpload = new XMLHttpRequestUpload();

    if (!mUpload) {
      aRv.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }
  }

  return mUpload;
}

void
XMLHttpRequestWorker::Send(JSContext* aCx, ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  if (!mProxy) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  RefPtr<SendRunnable> sendRunnable =
    new SendRunnable(mWorkerPrivate, mProxy, NullString());

  // Nothing to clone.
  SendInternal(sendRunnable, aRv);
}

void
XMLHttpRequestWorker::Send(JSContext* aCx, const nsAString& aBody,
                           ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  if (!mProxy) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  RefPtr<SendRunnable> sendRunnable =
    new SendRunnable(mWorkerPrivate, mProxy, aBody);

  // Nothing to clone.
  SendInternal(sendRunnable, aRv);
}

void
XMLHttpRequestWorker::Send(JSContext* aCx, JS::Handle<JSObject*> aBody,
                           ErrorResult& aRv)
{
  MOZ_ASSERT(aBody);

  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  if (!mProxy) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  JS::Rooted<JS::Value> valToClone(aCx);
  if (JS_IsArrayBufferObject(aBody) || JS_IsArrayBufferViewObject(aBody)) {
    valToClone.setObject(*aBody);
  }
  else {
    JS::Rooted<JS::Value> obj(aCx, JS::ObjectValue(*aBody));
    JSString* bodyStr = JS::ToString(aCx, obj);
    if (!bodyStr) {
      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
      return;
    }
    valToClone.setString(bodyStr);
  }

  RefPtr<SendRunnable> sendRunnable =
    new SendRunnable(mWorkerPrivate, mProxy, EmptyString());

  sendRunnable->Write(aCx, valToClone, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  SendInternal(sendRunnable, aRv);
}

void
XMLHttpRequestWorker::Send(JSContext* aCx, Blob& aBody, ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  if (!mProxy) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  JS::Rooted<JS::Value> value(aCx);
  if (!GetOrCreateDOMReflector(aCx, &aBody, &value)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  RefPtr<BlobImpl> blobImpl = aBody.Impl();
  MOZ_ASSERT(blobImpl);

  aRv = blobImpl->SetMutable(false);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  RefPtr<SendRunnable> sendRunnable =
    new SendRunnable(mWorkerPrivate, mProxy, EmptyString());

  sendRunnable->Write(aCx, value, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  SendInternal(sendRunnable, aRv);
}

void
XMLHttpRequestWorker::Send(JSContext* aCx, FormData& aBody, ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  if (!mProxy) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  JS::Rooted<JS::Value> value(aCx);
  if (!GetOrCreateDOMReflector(aCx, &aBody, &value)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  RefPtr<SendRunnable> sendRunnable =
    new SendRunnable(mWorkerPrivate, mProxy, EmptyString());

  sendRunnable->Write(aCx, value, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  SendInternal(sendRunnable, aRv);
}

void
XMLHttpRequestWorker::Send(JSContext* aCx, URLSearchParams& aBody,
                           ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  if (!mProxy) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  JS::Rooted<JS::Value> value(aCx);
  if (!GetOrCreateDOMReflector(aCx, &aBody, &value)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  RefPtr<SendRunnable> sendRunnable =
    new SendRunnable(mWorkerPrivate, mProxy, EmptyString());

  sendRunnable->Write(aCx, value, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  SendInternal(sendRunnable, aRv);
}

void
XMLHttpRequestWorker::Send(JSContext* aCx, const ArrayBuffer& aBody,
                           ErrorResult& aRv)
{
  JS::Rooted<JSObject*> obj(mWorkerPrivate->GetJSContext(), aBody.Obj());
  return Send(aCx, obj, aRv);
}

void
XMLHttpRequestWorker::Send(JSContext* aCx, const ArrayBufferView& aBody,
                           ErrorResult& aRv)
{
  if (JS_IsTypedArrayObject(aBody.Obj()) &&
      JS_GetTypedArraySharedness(aBody.Obj())) {
    // Throw if the object is mapping shared memory (must opt in).
    aRv.ThrowTypeError<MSG_TYPEDARRAY_IS_SHARED>(NS_LITERAL_STRING("Argument of XMLHttpRequest.send"));
    return;
  }
  JS::Rooted<JSObject*> obj(aCx, aBody.Obj());
  return Send(aCx, obj, aRv);
}

void
XMLHttpRequestWorker::Abort(ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  if (!mProxy) {
    return;
  }

  MaybeDispatchPrematureAbortEvents(aRv);
  if (aRv.Failed()) {
    return;
  }

  if (mStateData.mReadyState == 4) {
    // No one did anything to us while we fired abort events, so reset our state
    // to "unsent"
    mStateData.mReadyState = 0;
  }

  mProxy->mOuterEventStreamId++;

  RefPtr<AbortRunnable> runnable = new AbortRunnable(mWorkerPrivate, mProxy);
  runnable->Dispatch(aRv);
}

void
XMLHttpRequestWorker::GetResponseHeader(const nsACString& aHeader,
                                        nsACString& aResponseHeader, ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  if (!mProxy) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  nsCString responseHeader;
  RefPtr<GetResponseHeaderRunnable> runnable =
    new GetResponseHeaderRunnable(mWorkerPrivate, mProxy, aHeader,
                                  responseHeader);
  runnable->Dispatch(aRv);
  if (aRv.Failed()) {
    return;
  }
  aResponseHeader = responseHeader;
}

void
XMLHttpRequestWorker::GetAllResponseHeaders(nsACString& aResponseHeaders,
                                            ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  if (!mProxy) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  nsCString responseHeaders;
  RefPtr<GetAllResponseHeadersRunnable> runnable =
    new GetAllResponseHeadersRunnable(mWorkerPrivate, mProxy, responseHeaders);
  runnable->Dispatch(aRv);
  if (aRv.Failed()) {
    return;
  }

  aResponseHeaders = responseHeaders;
}

void
XMLHttpRequestWorker::OverrideMimeType(const nsAString& aMimeType, ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  // We're supposed to throw if the state is not OPENED or HEADERS_RECEIVED. We
  // can detect OPENED really easily but we can't detect HEADERS_RECEIVED in a
  // non-racy way until the XHR state machine actually runs on this thread
  // (bug 671047). For now we're going to let this work only if the Send()
  // method has not been called, unless the send has been aborted.
  if (!mProxy || (SendInProgress() &&
                  (mProxy->mSeenLoadStart ||
                   mStateData.mReadyState > nsIXMLHttpRequest::OPENED))) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  RefPtr<OverrideMimeTypeRunnable> runnable =
    new OverrideMimeTypeRunnable(mWorkerPrivate, mProxy, aMimeType);
  runnable->Dispatch(aRv);
}

void
XMLHttpRequestWorker::SetResponseType(XMLHttpRequestResponseType aResponseType,
                                      ErrorResult& aRv)
{
  mWorkerPrivate->AssertIsOnWorkerThread();

  if (mCanceled) {
    aRv.ThrowUncatchableException();
    return;
  }

  // "document" is fine for the main thread but not for a worker. Short-circuit
  // that here.
  if (aResponseType == XMLHttpRequestResponseType::Document) {
    return;
  }

  if (!mProxy) {
    // Open() has not been called yet. We store the responseType and we will use
    // it later in Open().
    mResponseType = aResponseType;
    return;
  }

  if (SendInProgress() &&
      (mProxy->mSeenLoadStart ||
       mStateData.mReadyState > nsIXMLHttpRequest::OPENED)) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  RefPtr<SetResponseTypeRunnable> runnable =
    new SetResponseTypeRunnable(mWorkerPrivate, mProxy, aResponseType);
  runnable->Dispatch(aRv);
  if (aRv.Failed()) {
    return;
  }

  mResponseType = runnable->ResponseType();
}

void
XMLHttpRequestWorker::GetResponse(JSContext* /* unused */,
                                  JS::MutableHandle<JS::Value> aResponse,
                                  ErrorResult& aRv)
{
  if (NS_SUCCEEDED(mStateData.mResponseTextResult) &&
      mStateData.mResponse.isUndefined()) {
    MOZ_ASSERT(NS_SUCCEEDED(mStateData.mResponseResult));

    if (mStateData.mResponseText.IsEmpty()) {
      mStateData.mResponse =
        JS_GetEmptyStringValue(mWorkerPrivate->GetJSContext());
    } else {
      XMLHttpRequestStringSnapshotReaderHelper helper(mStateData.mResponseText);

      JSString* str =
        JS_NewUCStringCopyN(mWorkerPrivate->GetJSContext(),
                            helper.Buffer(), helper.Length());

      if (!str) {
        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
        return;
      }

      mStateData.mResponse.setString(str);
    }
  }

  aRv = mStateData.mResponseResult;
  aResponse.set(mStateData.mResponse);
}

void
XMLHttpRequestWorker::GetResponseText(DOMString& aResponseText, ErrorResult& aRv)
{
  aRv = mStateData.mResponseTextResult;
  if (aRv.Failed()) {
    return;
  }

  if (!mStateData.mResponseText.GetAsString(aResponseText)) {
    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    return;
  }
}

void
XMLHttpRequestWorker::UpdateState(const StateData& aStateData,
                                  bool aUseCachedArrayBufferResponse)
{
  if (aUseCachedArrayBufferResponse) {
    MOZ_ASSERT(mStateData.mResponse.isObject() &&
               JS_IsArrayBufferObject(&mStateData.mResponse.toObject()));

    JS::Rooted<JS::Value> response(mWorkerPrivate->GetJSContext(),
                                   mStateData.mResponse);
    mStateData = aStateData;
    mStateData.mResponse = response;
  }
  else {
    mStateData = aStateData;
  }

  XMLHttpRequestBinding::ClearCachedResponseTextValue(this);
}

} // dom namespace
} // mozilla namespace