/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is worker threads.
 *
 * The Initial Developer of the Original Code is
 *   Mozilla Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2008
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Ben Turner <bent.mozilla@gmail.com> (Original Author)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsDOMWorkerXHR.h"

// Interfaces
#include "nsIDocument.h"
#include "nsIDOMEvent.h"
#include "nsIThread.h"
#include "nsIXPConnect.h"

// Other includes
#include "nsAutoLock.h"
#include "nsAXPCNativeCallContext.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsIClassInfoImpl.h"
#include "nsJSUtils.h"
#include "nsThreadUtils.h"

// DOMWorker includes
#include "nsDOMThreadService.h"
#include "nsDOMWorkerEvents.h"
#include "nsDOMWorkerPool.h"
#include "nsDOMWorkerXHRProxy.h"

// The list of event types that we support. This list and the defines based on
// it determine the sizes of the listener arrays in nsDOMWorkerXHRProxy. Make
// sure that any event types shared by both the XHR and Upload objects are
// together at the beginning of the list. Any changes made to this list may
// affect sMaxUploadEventTypes, so make sure that it is adjusted accordingly or
// things will break!
const char* const nsDOMWorkerXHREventTarget::sListenerTypes[] = {
  // nsIXMLHttpRequestEventTarget listeners.
  "abort",                             /* LISTENER_TYPE_ABORT */
  "error",                             /* LISTENER_TYPE_ERROR */
  "load",                              /* LISTENER_TYPE_LOAD */
  "loadstart",                         /* LISTENER_TYPE_LOADSTART */
  "progress",                          /* LISTENER_TYPE_PROGRESS */

  // nsIXMLHttpRequest listeners.
  "readystatechange"                   /* LISTENER_TYPE_READYSTATECHANGE */
};

// This should always be set to the length of sListenerTypes.
const PRUint32 nsDOMWorkerXHREventTarget::sMaxXHREventTypes =
  NS_ARRAY_LENGTH(nsDOMWorkerXHREventTarget::sListenerTypes);

// This should be set to the index of the first event type that is *not*
// supported by the Upload object.
const PRUint32 nsDOMWorkerXHREventTarget::sMaxUploadEventTypes =
  LISTENER_TYPE_READYSTATECHANGE;

// Enforce the invariant that the upload object supports no more event types
// than the xhr object.
PR_STATIC_ASSERT(nsDOMWorkerXHREventTarget::sMaxXHREventTypes >=
                 nsDOMWorkerXHREventTarget::sMaxUploadEventTypes);

NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerXHREventTarget,
                             nsDOMWorkerMessageHandler,
                             nsIXMLHttpRequestEventTarget)

PRUint32
nsDOMWorkerXHREventTarget::GetListenerTypeFromString(const nsAString& aString)
{
  for (PRUint32 index = 0; index < sMaxXHREventTypes; index++) {
    if (aString.EqualsASCII(sListenerTypes[index])) {
      return index;
    }
  }
  return PR_UINT32_MAX;
}

NS_IMETHODIMP
nsDOMWorkerXHREventTarget::GetOnabort(nsIDOMEventListener** aOnabort)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_ENSURE_ARG_POINTER(aOnabort);

  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_ABORT]);

  nsCOMPtr<nsIDOMEventListener> listener = GetOnXListener(type);
  listener.forget(aOnabort);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHREventTarget::SetOnabort(nsIDOMEventListener* aOnabort)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_ABORT]);

  return SetOnXListener(type, aOnabort);
}

NS_IMETHODIMP
nsDOMWorkerXHREventTarget::GetOnerror(nsIDOMEventListener** aOnerror)
{
  NS_ENSURE_ARG_POINTER(aOnerror);
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_ERROR]);

  nsCOMPtr<nsIDOMEventListener> listener = GetOnXListener(type);
  listener.forget(aOnerror);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHREventTarget::SetOnerror(nsIDOMEventListener* aOnerror)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_ERROR]);

  return SetOnXListener(type, aOnerror);
}

NS_IMETHODIMP
nsDOMWorkerXHREventTarget::GetOnload(nsIDOMEventListener** aOnload)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_ENSURE_ARG_POINTER(aOnload);

  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_LOAD]);

  nsCOMPtr<nsIDOMEventListener> listener = GetOnXListener(type);
  listener.forget(aOnload);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHREventTarget::SetOnload(nsIDOMEventListener* aOnload)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_LOAD]);

  return SetOnXListener(type, aOnload);
}

NS_IMETHODIMP
nsDOMWorkerXHREventTarget::GetOnloadstart(nsIDOMEventListener** aOnloadstart)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_ENSURE_ARG_POINTER(aOnloadstart);

  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_LOADSTART]);

  nsCOMPtr<nsIDOMEventListener> listener = GetOnXListener(type);
  listener.forget(aOnloadstart);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHREventTarget::SetOnloadstart(nsIDOMEventListener* aOnloadstart)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_LOADSTART]);

  return SetOnXListener(type, aOnloadstart);
}

NS_IMETHODIMP
nsDOMWorkerXHREventTarget::GetOnprogress(nsIDOMEventListener** aOnprogress)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_ENSURE_ARG_POINTER(aOnprogress);

  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_PROGRESS]);

  nsCOMPtr<nsIDOMEventListener> listener = GetOnXListener(type);
  listener.forget(aOnprogress);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHREventTarget::SetOnprogress(nsIDOMEventListener* aOnprogress)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_PROGRESS]);

  return SetOnXListener(type, aOnprogress);
}

nsDOMWorkerXHRUpload::nsDOMWorkerXHRUpload(nsDOMWorkerXHR* aWorkerXHR)
: mWorkerXHR(aWorkerXHR)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_ASSERTION(aWorkerXHR, "Null pointer!");
}

NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerXHRUpload, nsDOMWorkerXHREventTarget,
                                                   nsIXMLHttpRequestUpload)

NS_IMPL_CI_INTERFACE_GETTER3(nsDOMWorkerXHRUpload, nsIDOMEventTarget,
                                                   nsIXMLHttpRequestEventTarget,
                                                   nsIXMLHttpRequestUpload)

NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerXHRUpload)

NS_IMETHODIMP
nsDOMWorkerXHRUpload::AddEventListener(const nsAString& aType,
                                       nsIDOMEventListener* aListener,
                                       PRBool aUseCapture)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_ENSURE_ARG_POINTER(aListener);

  if (mWorkerXHR->mWorker->IsCanceled()) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = nsDOMWorkerXHREventTarget::AddEventListener(aType, aListener,
                                                            aUseCapture);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = mWorkerXHR->mXHRProxy->UploadEventListenerAdded();
  if (NS_FAILED(rv)) {
    NS_WARNING("UploadEventListenerAdded failed!");
    RemoveEventListener(aType, aListener, aUseCapture);
    return rv;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHRUpload::RemoveEventListener(const nsAString& aType,
                                          nsIDOMEventListener* aListener,
                                          PRBool aUseCapture)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_ENSURE_ARG_POINTER(aListener);

  if (mWorkerXHR->mWorker->IsCanceled()) {
    return NS_ERROR_ABORT;
  }

  return nsDOMWorkerXHREventTarget::RemoveEventListener(aType, aListener,
                                                        aUseCapture);
}

NS_IMETHODIMP
nsDOMWorkerXHRUpload::DispatchEvent(nsIDOMEvent* aEvent,
                                    PRBool* _retval)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_ENSURE_ARG_POINTER(aEvent);

  if (mWorkerXHR->mWorker->IsCanceled()) {
    return NS_ERROR_ABORT;
  }

  return nsDOMWorkerXHREventTarget::DispatchEvent(aEvent, _retval);
}

nsresult
nsDOMWorkerXHRUpload::SetOnXListener(const nsAString& aType,
                                     nsIDOMEventListener* aListener)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mWorkerXHR->mCanceled) {
    return NS_ERROR_ABORT;
  }

  PRUint32 type = GetListenerTypeFromString(aType);
  if (type > sMaxUploadEventTypes) {
    // Silently ignore junk events.
    return NS_OK;
  }

  return nsDOMWorkerXHREventTarget::SetOnXListener(aType, aListener);
}

nsDOMWorkerXHR::nsDOMWorkerXHR(nsDOMWorker* aWorker)
: nsDOMWorkerFeature(aWorker),
  mWrappedNative(nsnull),
  mCanceled(PR_FALSE)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_ASSERTION(aWorker, "Must have a worker!");
}

nsDOMWorkerXHR::~nsDOMWorkerXHR()
{
  if (mXHRProxy) {
    if (!NS_IsMainThread()) {
      nsCOMPtr<nsIRunnable> runnable =
        NS_NEW_RUNNABLE_METHOD(nsDOMWorkerXHRProxy, mXHRProxy.get(), Destroy);

      if (runnable) {
        mXHRProxy = nsnull;
        NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
      }
    }
    else {
      mXHRProxy->Destroy();
    }
  }
}

// Tricky! We use the AddRef/Release method of nsDOMWorkerFeature (to make sure
// we properly remove ourselves from the worker array) but inherit the QI of
// nsDOMWorkerXHREventTarget.
NS_IMPL_ADDREF_INHERITED(nsDOMWorkerXHR, nsDOMWorkerFeature)
NS_IMPL_RELEASE_INHERITED(nsDOMWorkerXHR, nsDOMWorkerFeature)

NS_IMPL_QUERY_INTERFACE_INHERITED2(nsDOMWorkerXHR, nsDOMWorkerXHREventTarget,
                                                   nsIXMLHttpRequest,
                                                   nsIXPCScriptable)

NS_IMPL_CI_INTERFACE_GETTER3(nsDOMWorkerXHR, nsIDOMEventTarget,
                                             nsIXMLHttpRequestEventTarget,
                                             nsIXMLHttpRequest)

NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerXHR)

#define XPC_MAP_CLASSNAME nsDOMWorkerXHR
#define XPC_MAP_QUOTED_CLASSNAME "XMLHttpRequest"
#define XPC_MAP_WANT_POSTCREATE
#define XPC_MAP_WANT_TRACE
#define XPC_MAP_WANT_FINALIZE

#define XPC_MAP_FLAGS                                  \
  nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE        | \
  nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY        | \
  nsIXPCScriptable::DONT_REFLECT_INTERFACE_NAMES

#include "xpc_map_end.h"

NS_IMETHODIMP
nsDOMWorkerXHR::Trace(nsIXPConnectWrappedNative* /* aWrapper */,
                      JSTracer* aTracer,
                      JSObject* /*aObj */)
{
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");

  if (!mCanceled) {
    nsDOMWorkerMessageHandler::Trace(aTracer);
    if (mUpload) {
      mUpload->Trace(aTracer);
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::Finalize(nsIXPConnectWrappedNative* /* aWrapper */,
                         JSContext* /* aCx */,
                         JSObject* /* aObj */)
{
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");

  nsDOMWorkerMessageHandler::ClearAllListeners();

  if (mUpload) {
    mUpload->ClearAllListeners();
  }

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::PostCreate(nsIXPConnectWrappedNative* aWrapper,
                           JSContext* /* aCx */,
                           JSObject* /* aObj */)
{
  mWrappedNative = aWrapper;
  return NS_OK;
}

nsresult
nsDOMWorkerXHR::Init()
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  nsRefPtr<nsDOMWorkerXHRProxy> proxy = new nsDOMWorkerXHRProxy(this);
  NS_ENSURE_TRUE(proxy, NS_ERROR_OUT_OF_MEMORY);

  nsresult rv = proxy->Init();
  NS_ENSURE_SUCCESS(rv, rv);

  proxy.swap(mXHRProxy);
  return NS_OK;
}

void
nsDOMWorkerXHR::Cancel()
{
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");

  // Just in case mUpload holds the only ref to this object we make sure to stay
  // alive through this call.
  nsRefPtr<nsDOMWorkerXHR> kungFuDeathGrip(this);

  {
    // This lock is here to prevent a race between Cancel and GetUpload, not to
    // protect mCanceled.
    nsAutoLock lock(mWorker->Lock());

    mCanceled = PR_TRUE;
    mUpload = nsnull;
  }

  if (mXHRProxy) {
    mXHRProxy->Destroy();
    mXHRProxy = nsnull;
  }

  mWorker = nsnull;
}

nsresult
nsDOMWorkerXHR::SetOnXListener(const nsAString& aType,
                               nsIDOMEventListener* aListener)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  PRUint32 type = GetListenerTypeFromString(aType);
  if (type > sMaxXHREventTypes) {
    // Silently ignore junk events.
    return NS_OK;
  }

  return nsDOMWorkerXHREventTarget::SetOnXListener(aType, aListener);
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetChannel(nsIChannel** aChannel)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  NS_ENSURE_ARG_POINTER(aChannel);
  *aChannel = nsnull;
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetResponseXML(nsIDOMDocument** aResponseXML)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  NS_ENSURE_ARG_POINTER(aResponseXML);
  *aResponseXML = nsnull;
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetResponseText(nsAString& aResponseText)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = mXHRProxy->GetResponseText(aResponseText);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetStatus(PRUint32* aStatus)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_ENSURE_ARG_POINTER(aStatus);

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = mXHRProxy->GetStatus(aStatus);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetStatusText(nsACString& aStatusText)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = mXHRProxy->GetStatusText(aStatusText);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::Abort()
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = mXHRProxy->Abort();
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetAllResponseHeaders(char** _retval)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_ENSURE_ARG_POINTER(_retval);

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = mXHRProxy->GetAllResponseHeaders(_retval);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetResponseHeader(const nsACString& aHeader,
                                  nsACString& _retval)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = mXHRProxy->GetResponseHeader(aHeader, _retval);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::OpenRequest(const nsACString& aMethod,
                            const nsACString& aUrl,
                            PRBool aAsync,
                            const nsAString& aUser,
                            const nsAString& aPassword)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = mXHRProxy->OpenRequest(aMethod, aUrl, aAsync, aUser, aPassword);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::Open(const nsACString& aMethod,
                     const nsACString& aUrl)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  PRBool async = PR_TRUE;
  nsAutoString user, password;

  nsIXPConnect* xpc = nsContentUtils::XPConnect();
  NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED);

  nsAXPCNativeCallContext* cc;
  nsresult rv = xpc->GetCurrentNativeCallContext(&cc);

  do {
    if (NS_FAILED(rv) || !cc) {
      break;
    }

    PRUint32 argc;
    rv = cc->GetArgc(&argc);
    NS_ENSURE_SUCCESS(rv, rv);

    if (argc < 3) {
      break;
    }

    jsval* argv;
    rv = cc->GetArgvPtr(&argv);
    NS_ENSURE_SUCCESS(rv, rv);

    JSContext* cx;
    rv = cc->GetJSContext(&cx);
    NS_ENSURE_SUCCESS(rv, rv);

    JSAutoRequest ar(cx);

    JSBool asyncBool;
    JS_ValueToBoolean(cx, argv[2], &asyncBool);
    async = (PRBool)asyncBool;

    if (argc < 4) {
      break;
    }

    JSString* argStr;
    if (!JSVAL_IS_NULL(argv[3]) && !JSVAL_IS_VOID(argv[3])) {
      argStr = JS_ValueToString(cx, argv[3]);
      if (argStr) {
        user.Assign(nsDependentJSString(argStr));
      }
    }

    if (argc < 5) {
      break;
    }

    if (!JSVAL_IS_NULL(argv[4]) && !JSVAL_IS_VOID(argv[4])) {
      argStr = JS_ValueToString(cx, argv[4]);
      if (argStr) {
        password.Assign(nsDependentJSString(argStr));
      }
    }
  } while (PR_FALSE);

  return OpenRequest(aMethod, aUrl, async, user, password);
}

NS_IMETHODIMP
nsDOMWorkerXHR::Send(nsIVariant* aBody)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  if (mWorker->IsClosing() && !mXHRProxy->mSyncRequest) {
    // Cheat and don't start this request since we know we'll never be able to
    // use the data.
    return NS_OK;
  }

  nsresult rv = mXHRProxy->Send(aBody);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::SendAsBinary(const nsAString& aBody)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  if (mWorker->IsClosing() && !mXHRProxy->mSyncRequest) {
    // Cheat and don't start this request since we know we'll never be able to
    // use the data.
    return NS_OK;
  }

  nsresult rv = mXHRProxy->SendAsBinary(aBody);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::SetRequestHeader(const nsACString& aHeader,
                                 const nsACString& aValue)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = mXHRProxy->SetRequestHeader(aHeader, aValue);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetReadyState(PRInt32* aReadyState)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  NS_ENSURE_ARG_POINTER(aReadyState);

  nsresult rv = mXHRProxy->GetReadyState(aReadyState);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::OverrideMimeType(const nsACString& aMimetype)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = mXHRProxy->OverrideMimeType(aMimetype);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetMultipart(PRBool* aMultipart)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  NS_ENSURE_ARG_POINTER(aMultipart);

  nsresult rv = mXHRProxy->GetMultipart(aMultipart);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::SetMultipart(PRBool aMultipart)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = mXHRProxy->SetMultipart(aMultipart);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetMozBackgroundRequest(PRBool* aMozBackgroundRequest)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  NS_ENSURE_ARG_POINTER(aMozBackgroundRequest);

  *aMozBackgroundRequest = PR_FALSE;
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::SetMozBackgroundRequest(PRBool aMozBackgroundRequest)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (aMozBackgroundRequest) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::Init(nsIPrincipal* aPrincipal,
                     nsIScriptContext* aScriptContext,
                     nsPIDOMWindow* aOwnerWindow,
                     nsIURI* aBaseURI)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
  NS_NOTREACHED("No one should be calling this!");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetUpload(nsIXMLHttpRequestUpload** aUpload)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  nsRefPtr<nsDOMWorker> worker = mWorker;
  if (!worker) {
    return NS_ERROR_ABORT;
  }

  nsAutoLock lock(worker->Lock());

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  NS_ENSURE_ARG_POINTER(aUpload);

  if (!mUpload) {
    mUpload = new nsDOMWorkerXHRUpload(this);
    NS_ENSURE_TRUE(mUpload, NS_ERROR_OUT_OF_MEMORY);
  }

  NS_ADDREF(*aUpload = mUpload);
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetOnreadystatechange(nsIDOMEventListener** aOnreadystatechange)
{
  NS_ENSURE_ARG_POINTER(aOnreadystatechange);

  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_READYSTATECHANGE]);

  nsCOMPtr<nsIDOMEventListener> listener = GetOnXListener(type);
  listener.forget(aOnreadystatechange);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::SetOnreadystatechange(nsIDOMEventListener* aOnreadystatechange)
{
  nsAutoString type;
  type.AssignASCII(sListenerTypes[LISTENER_TYPE_READYSTATECHANGE]);

  return SetOnXListener(type, aOnreadystatechange);
}

NS_IMETHODIMP
nsDOMWorkerXHR::GetWithCredentials(PRBool* aWithCredentials)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  NS_ENSURE_ARG_POINTER(aWithCredentials);

  nsresult rv = mXHRProxy->GetWithCredentials(aWithCredentials);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerXHR::SetWithCredentials(PRBool aWithCredentials)
{
  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");

  if (mCanceled) {
    return NS_ERROR_ABORT;
  }

  nsresult rv = mXHRProxy->SetWithCredentials(aWithCredentials);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}
