dom/xhr/XMLHttpRequestWorker.cpp
author Jeff Walden <jwalden@mit.edu>
Mon, 04 Mar 2019 15:19:16 -0800
changeset 520218 f4e9e5429fd82f9c3787ca2d42798a2bf3d353f9
parent 520200 a7bb6e5afaf2d5afa0787244ff1b7fad07c8c9c1
child 520529 9da3ab33cf679f8a11cbfcd87e93fc5826fe95cd
permissions -rw-r--r--
Bug 1532005 - Implement a new js/public/ArrayBuffer.h header to centralize functionality for creating and interacting with ArrayBuffers. r=sfink Differential Revision: https://phabricator.services.mozilla.com/D21827

/* -*- 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 "nsIDOMEventListener.h"
#include "nsIRunnable.h"
#include "nsIXPConnect.h"

#include "jsapi.h"  // JS::AutoValueArray
#include "jsfriendapi.h"
#include "js/ArrayBuffer.h"  // JS::Is{,Detached}ArrayBufferObject
#include "js/GCPolicyAPI.h"
#include "js/RootingAPI.h"  // JS::{Handle,Heap},PersistentRooted
#include "js/TracingAPI.h"
#include "js/Value.h"  // JS::{Undefined,}Value
#include "mozilla/ArrayUtils.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/ProgressEvent.h"
#include "mozilla/dom/StructuredCloneHolder.h"
#include "mozilla/dom/UnionConversions.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/Telemetry.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsJSUtils.h"
#include "nsThreadUtils.h"

#include "XMLHttpRequestUpload.h"

#include "mozilla/UniquePtr.h"

namespace mozilla {
namespace dom {

/* 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;
  const ClientInfo mClientInfo;
  const Maybe<ServiceWorkerDescriptor> mController;

  // XHR Params:
  bool mMozAnon;
  bool mMozSystem;

  // Only touched on the main thread.
  RefPtr<XMLHttpRequestMainThread> mXHR;
  RefPtr<XMLHttpRequestUpload> 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, const ClientInfo& aClientInfo,
        const Maybe<ServiceWorkerDescriptor>& aController, bool aMozAnon,
        bool aMozSystem)
      : mWorkerPrivate(nullptr),
        mXMLHttpRequestPrivate(aXHRPrivate),
        mClientInfo(aClientInfo),
        mController(aController),
        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(WorkerStatus aFailStatus, ErrorResult& aRv) {
    WorkerMainThreadRunnable::Dispatch(aFailStatus, 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[] = {
    // XMLHttpRequestEventTarget event types, supported by both XHR and Upload.
    "abort",
    "error",
    "load",
    "loadstart",
    "progress",
    "timeout",

    // XMLHttpRequest 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) override {
    if (mXMLHttpRequestPrivate->SendInProgress()) {
      mXMLHttpRequestPrivate->Unpin();
    }

    return true;
  }
};

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

 public:
  explicit AsyncTeardownRunnable(Proxy* aProxy)
      : Runnable("dom::AsyncTeardownRunnable"), 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)
      : Runnable("dom::LoadStartDetectionRunnable"),
        mWorkerPrivate(aProxy->mWorkerPrivate),
        mProxy(aProxy),
        mXHR(aProxy->mXHR),
        mXMLHttpRequestPrivate(aXHRPrivate),
        mChannelId(mProxy->mInnerChannelId),
        mReceivedLoadStart(false) {
    AssertIsOnMainThread();
    mEventType.AssignASCII(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))) {
      NS_WARNING("Failed to add event listener!");
      return false;
    }

    return NS_SUCCEEDED(mWorkerPrivate->DispatchToMainThread(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() {}

  bool PreDispatch(WorkerPrivate* /* unused */) final;
  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;
  }

  mXHR = new XMLHttpRequestMainThread();
  mXHR->Construct(mWorkerPrivate->GetPrincipal(),
                  ownerWindow ? ownerWindow->AsGlobal() : nullptr,
                  mWorkerPrivate->GetBaseURI(), mWorkerPrivate->GetLoadGroup(),
                  mWorkerPrivate->GetPerformanceStorage(),
                  mWorkerPrivate->CSPEventListener());

  mXHR->SetParameters(mMozAnon, mMozSystem);
  mXHR->SetClientInfoAndController(mClientInfo, mController);

  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()) {
          MOZ_CRASH("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()) {
          MOZ_CRASH("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!");

  RefPtr<DOMEventTargetHelper> targetHelper =
      aUpload ? static_cast<XMLHttpRequestUpload*>(mXHRUpload.get())
              : static_cast<XMLHttpRequestEventTarget*>(mXHR.get());
  MOZ_ASSERT(targetHelper, "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(targetHelper->AddEventListener(eventType, this, false))) {
        return false;
      }
    } else {
      targetHelper->RemoveEventListener(eventType, this, false);
    }
  }

  if (aUpload) {
    mUploadEventListenersAttached = aAdd;
  }

  return true;
}

NS_IMPL_ISUPPORTS(Proxy, nsIDOMEventListener)

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

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

  nsAutoString type;
  aEvent->GetType(type);

  bool isUploadTarget = mXHR != aEvent->GetTarget();
  ProgressEvent* progressEvent = aEvent->AsProgressEvent();

  if (mInOpen && type.EqualsASCII(sEventStrings[STRING_readystatechange])) {
    if (mXHR->ReadyState() == 1) {
      mInnerEventStreamId++;
    }
  }

  {
    AutoSafeJSContext 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) {
      if (!mIsSyncXHR || !type.EqualsASCII(sEventStrings[STRING_progress])) {
        runnable = new EventRunnable(
            this, isUploadTarget, type, progressEvent->LengthComputable(),
            progressEvent->Loaded(), progressEvent->Total(), scope);
      }
    } else {
      runnable = new EventRunnable(this, isUploadTarget, type, scope);
    }

    if (runnable) {
      runnable->Dispatch();
    }
  }

  if (!isUploadTarget) {
    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();

  mXHR->RemoveEventListener(mEventType, this, false);

  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(Event* aEvent) {
  AssertIsOnMainThread();

#ifdef DEBUG
  {
    nsAutoString type;
    aEvent->GetType(type);
    MOZ_ASSERT(type == mEventType);
  }
#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);

  const EnumEntry& entry =
      XMLHttpRequestResponseTypeValues::strings[static_cast<uint32_t>(
          xhr->ResponseType())];
  mResponseType.AssignASCII(entry.value, entry.length);

  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);
    xhr->GetResponse(cx, &response, rv);
    mResponseResult = rv.StealNSResult();
    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.isObject() ? &response.toObject() : 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() == 4) {
                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;
          }
        }
      }
    }
  }

  mStatus = xhr->GetStatus(rv);
  mStatusResult = rv.StealNSResult();

  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 (mUploadEvent && !xhr->GetUploadObjectNoCreate()) {
    return true;
  }

  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->DispatchEvent(*event);

  // After firing the event set mResponse to 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;
  }

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

  ErrorResult rv;

  if (mWithCredentials) {
    mProxy->mXHR->SetWithCredentials(mWithCredentials, rv);
    if (NS_WARN_IF(rv.Failed())) {
      return rv.StealNSResult();
    }
  }

  if (mTimeout) {
    mProxy->mXHR->SetTimeout(mTimeout, rv);
    if (NS_WARN_IF(rv.Failed())) {
      return rv.StealNSResult();
    }
  }

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

  mProxy->mXHR->Open(
      mMethod, mURL, true, mUser.WasPassed() ? mUser.Value() : VoidString(),
      mPassword.WasPassed() ? mPassword.Value() : VoidString(), rv);

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

  if (NS_WARN_IF(rv.Failed())) {
    return rv.StealNSResult();
  }

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

  return NS_OK;
}

void SendRunnable::RunOnMainThread(ErrorResult& aRv) {
  Nullable<
      DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString>
      payload;

  if (HasData()) {
    AutoSafeJSContext cx;

    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;
    }

    Maybe<
        DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVStringArgument>
        holder;
    holder.emplace(payload.SetValue());
    bool done = false, failed = false, tryNext;

    if (body.isObject()) {
      done = (failed =
                  !holder.ref().TrySetToDocument(cx, &body, tryNext, false)) ||
             !tryNext ||
             (failed = !holder.ref().TrySetToBlob(cx, &body, tryNext, false)) ||
             !tryNext ||
             (failed = !holder.ref().TrySetToArrayBufferView(cx, &body, tryNext,
                                                             false)) ||
             !tryNext ||
             (failed = !holder.ref().TrySetToArrayBuffer(cx, &body, tryNext,
                                                         false)) ||
             !tryNext ||
             (failed =
                  !holder.ref().TrySetToFormData(cx, &body, tryNext, false)) ||
             !tryNext ||
             (failed = !holder.ref().TrySetToURLSearchParams(cx, &body, tryNext,
                                                             false)) ||
             !tryNext;
    }

    if (!done) {
      done = (failed = !holder.ref().TrySetToUSVString(cx, &body, tryNext)) ||
             !tryNext;
    }
    if (failed || !done) {
      aRv.Throw(NS_ERROR_FAILURE);
      return;
    }
  } else {
    DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString&
        ref = payload.SetValue();
    ref.SetAsUSVString().Rebind(mStringBody.Data(), mStringBody.Length());
  }

  // 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++;

  mProxy->mXHR->Send(nullptr, payload, aRv);

  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::_empty),
      mTimeout(0),
      mBackgroundRequest(false),
      mWithCredentials(false),
      mCanceled(false),
      mMozAnon(false),
      mMozSystem(false) {
  mWorkerPrivate->AssertIsOnWorkerThread();

  mozilla::HoldJSObjects(this);
}

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

  ReleaseProxy(XHRIsGoingAway);

  MOZ_ASSERT(!mWorkerRef);

  mozilla::DropJSObjects(this);
}

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

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(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);

  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;

      IgnoredErrorResult forAssertionsOnly;
      // This runnable _must_ be executed.
      runnable->Dispatch(Dead, forAssertionsOnly);
      MOZ_DIAGNOSTIC_ASSERT(!forAssertionsOnly.Failed());
    }
  }
}

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

  if (mWorkerRef) {
    return;
  }

  RefPtr<XMLHttpRequestWorker> self = this;
  mWorkerRef =
      StrongWorkerRef::Create(mWorkerPrivate, "XMLHttpRequestWorker", [self]() {
        if (!self->mCanceled) {
          self->mCanceled = true;
          self->ReleaseProxy(WorkerIsGoingAway);
        }
      });
  if (NS_WARN_IF(!mWorkerRef)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  NS_ADDREF_THIS();
}

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

  // Only send readystatechange event when state changed.
  bool isStateChanged = false;
  if ((mStateData.mReadyState == 1 && mStateData.mFlagSend) ||
      mStateData.mReadyState == 2 || mStateData.mReadyState == 3) {
    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 {
    if (mProxy->mIsSyncXHR &&
        aEventType.EqualsASCII(sEventStrings[STRING_progress])) {
      return;
    }

    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->DispatchEvent(*event);
}

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

  MOZ_ASSERT(mWorkerRef, "Mismatched calls to Unpin!");
  mWorkerRef = nullptr;

  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, Canceling);
    syncLoopTarget = autoSyncLoop->GetEventTarget();
    if (!syncLoopTarget) {
      aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
      return;
    }
  }

  mProxy->mOuterChannelId++;

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

  mStateData.mFlagSend = true;

  aRunnable->Dispatch(Canceling, 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 (!mWorkerRef) {
      autoUnpin.Clear();
    }
    return;
  }

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

  autoUnpin.Clear();

  bool succeeded = autoSyncLoop->Run();
  mStateData.mFlagSend = false;

  // 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 (!succeeded && !aRv.Failed()) {
    aRv.Throw(NS_ERROR_FAILURE);
  }
}

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 {
    Maybe<ClientInfo> clientInfo(mWorkerPrivate->GetClientInfo());
    if (clientInfo.isNothing()) {
      aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
      return;
    }
    mProxy = new Proxy(this, clientInfo.ref(), mWorkerPrivate->GetController(),
                       mMozAnon, mMozSystem);
  }

  mProxy->mOuterEventStreamId++;

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

  ++mProxy->mOpenCount;
  runnable->Dispatch(Canceling, 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(Canceling, 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(Canceling, 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(Canceling, 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(Canceling, 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,
    const Nullable<
        DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString>&
        aData,
    ErrorResult& aRv) {
  mWorkerPrivate->AssertIsOnWorkerThread();

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

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

  RefPtr<SendRunnable> sendRunnable;

  if (aData.IsNull()) {
    sendRunnable = new SendRunnable(mWorkerPrivate, mProxy, VoidString());
    // Nothing to clone.
  }

  else if (aData.Value().IsDocument()) {
    MOZ_CRASH("Documents are not exposed to workers.");
  }

  else if (aData.Value().IsBlob()) {
    RefPtr<Blob> blob = &aData.Value().GetAsBlob();
    MOZ_ASSERT(blob);

    RefPtr<BlobImpl> blobImpl = blob->Impl();
    MOZ_ASSERT(blobImpl);

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

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

    sendRunnable = new SendRunnable(mWorkerPrivate, mProxy, EmptyString());

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

  else if (aData.Value().IsArrayBuffer()) {
    sendRunnable = new SendRunnable(mWorkerPrivate, mProxy, EmptyString());

    JS::Rooted<JS::Value> value(aCx);
    value.setObject(*aData.Value().GetAsArrayBuffer().Obj());

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

  else if (aData.Value().IsArrayBufferView()) {
    const ArrayBufferView& body = aData.Value().GetAsArrayBufferView();

    if (JS_IsTypedArrayObject(body.Obj()) &&
        JS_GetTypedArraySharedness(body.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;
    }

    sendRunnable = new SendRunnable(mWorkerPrivate, mProxy, EmptyString());

    JS::Rooted<JS::Value> value(aCx);
    value.setObject(*body.Obj());

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

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

    sendRunnable = new SendRunnable(mWorkerPrivate, mProxy, EmptyString());

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

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

    sendRunnable = new SendRunnable(mWorkerPrivate, mProxy, EmptyString());

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

  else if (aData.Value().IsUSVString()) {
    sendRunnable = new SendRunnable(mWorkerPrivate, mProxy,
                                    aData.Value().GetAsUSVString());
    // Nothing to clone.
  }

  MOZ_ASSERT(sendRunnable);
  SendInternal(sendRunnable, aRv);
}

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

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

  if (!mProxy) {
    return;
  }

  // Set our status to 0 and statusText to "" if we
  // will be aborting an ongoing fetch, so the upcoming
  // abort events we dispatch have the correct info.
  if ((mStateData.mReadyState == XMLHttpRequest_Binding::OPENED &&
       mStateData.mFlagSend) ||
      mStateData.mReadyState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
      mStateData.mReadyState == XMLHttpRequest_Binding::LOADING ||
      mStateData.mReadyState == XMLHttpRequest_Binding::DONE) {
    mStateData.mStatus = 0;
    mStateData.mStatusText.Truncate();
  }

  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(Canceling, 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(Canceling, 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(Canceling, 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 > 1))) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  RefPtr<OverrideMimeTypeRunnable> runnable =
      new OverrideMimeTypeRunnable(mWorkerPrivate, mProxy, aMimeType);
  runnable->Dispatch(Canceling, 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 > 1)) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  RefPtr<SetResponseTypeRunnable> runnable =
      new SetResponseTypeRunnable(mWorkerPrivate, mProxy, aResponseType);
  runnable->Dispatch(Canceling, 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;
  }

  XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
}

}  // namespace dom
}  // namespace mozilla