/* -*- 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 Web Workers.
 *
 * 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 "nsDOMWorkerEvents.h"

#include "nsIXMLHttpRequest.h"
#include "nsIXPConnect.h"

#include "nsAXPCNativeCallContext.h"
#include "nsContentUtils.h"
#include "nsThreadUtils.h"

#include "nsDOMWorkerMessageHandler.h"
#include "nsDOMWorkerXHR.h"
#include "nsDOMWorkerXHRProxy.h"

NS_DEFINE_STATIC_IID_ACCESSOR(nsIDOMWorkerPrivateEvent,
                              NS_IDOMWORKERPRIVATEEVENT_IID)

nsDOMWorkerPrivateEvent::nsDOMWorkerPrivateEvent(nsIDOMEvent* aEvent)
: mEvent(aEvent),
  mProgressEvent(do_QueryInterface(aEvent)),
  mMessageEvent(do_QueryInterface(aEvent)),
  mErrorEvent(do_QueryInterface(aEvent)),
  mPreventDefaultCalled(PR_FALSE)
{
  NS_ASSERTION(aEvent, "Null pointer!");
}

NS_IMPL_THREADSAFE_ADDREF(nsDOMWorkerPrivateEvent)
NS_IMPL_THREADSAFE_RELEASE(nsDOMWorkerPrivateEvent)

NS_INTERFACE_MAP_BEGIN(nsDOMWorkerPrivateEvent)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMWorkerPrivateEvent)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEvent, nsIDOMWorkerPrivateEvent)
  NS_INTERFACE_MAP_ENTRY(nsIDOMWorkerPrivateEvent)
  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMProgressEvent, mProgressEvent)
  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIWorkerMessageEvent, mMessageEvent)
  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIWorkerErrorEvent, mErrorEvent)
  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
NS_INTERFACE_MAP_END

NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerPrivateEvent, nsIDOMEvent)

NS_IMPL_THREADSAFE_DOM_CI_HELPER(nsDOMWorkerPrivateEvent)
NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(nsDOMWorkerPrivateEvent)

NS_IMETHODIMP
nsDOMWorkerPrivateEvent::GetInterfaces(PRUint32* aCount, nsIID*** aArray)
{
  nsCOMPtr<nsIClassInfo> ci(do_QueryInterface(mEvent));
  if (ci) {
    return ci->GetInterfaces(aCount, aArray);
  }
  return NS_CI_INTERFACE_GETTER_NAME(nsDOMWorkerPrivateEvent)(aCount, aArray);
}

NS_IMETHODIMP
nsDOMWorkerPrivateEvent::PreventDefault()
{
  PRBool cancelable = PR_FALSE;
  mEvent->GetCancelable(&cancelable);

  if (cancelable) {
    mPreventDefaultCalled = PR_TRUE;
  }

  return mEvent->PreventDefault();
}

NS_IMETHODIMP
nsDOMWorkerPrivateEvent::InitEvent(const nsAString& aEventType,
                                   PRBool aCanBubble,
                                   PRBool aCancelable)
{
  mPreventDefaultCalled = PR_FALSE;
  return mEvent->InitEvent(aEventType, aCanBubble, aCancelable);
}

NS_IMETHODIMP
nsDOMWorkerPrivateEvent::InitProgressEvent(const nsAString& aTypeArg,
                                           PRBool aCanBubbleArg,
                                           PRBool aCancelableArg,
                                           PRBool aLengthComputableArg,
                                           PRUint64 aLoadedArg,
                                           PRUint64 aTotalArg)
{
  NS_ASSERTION(mProgressEvent, "Impossible!");

  mPreventDefaultCalled = PR_FALSE;
  return mProgressEvent->InitProgressEvent(aTypeArg, aCanBubbleArg,
                                           aCancelableArg, aLengthComputableArg,
                                           aLoadedArg, aTotalArg);
}

NS_IMETHODIMP
nsDOMWorkerPrivateEvent::InitMessageEvent(const nsAString& aTypeArg,
                                          PRBool aCanBubbleArg,
                                          PRBool aCancelableArg,
                                          const nsAString& aDataArg,
                                          const nsAString& aOriginArg,
                                          nsISupports* aSourceArg)
{
  NS_ASSERTION(mMessageEvent, "Impossible!");

  mPreventDefaultCalled = PR_FALSE;
  return mMessageEvent->InitMessageEvent(aTypeArg, aCanBubbleArg,
                                         aCancelableArg, aDataArg, aOriginArg,
                                         aSourceArg);
}

NS_IMETHODIMP
nsDOMWorkerPrivateEvent::InitErrorEvent(const nsAString& aTypeArg,
                                        PRBool aCanBubbleArg,
                                        PRBool aCancelableArg,
                                        const nsAString& aMessageArg,
                                        const nsAString& aFilenameArg,
                                        PRUint32 aLinenoArg)
{
  NS_ASSERTION(mErrorEvent, "Impossible!");

  mPreventDefaultCalled = PR_FALSE;
  return mErrorEvent->InitErrorEvent(aTypeArg, aCanBubbleArg, aCancelableArg,
                                     aMessageArg, aFilenameArg, aLinenoArg);
}

PRBool
nsDOMWorkerPrivateEvent::PreventDefaultCalled()
{
  return mPreventDefaultCalled;
}

NS_IMPL_THREADSAFE_ISUPPORTS2(nsDOMWorkerEvent, nsIDOMEvent,
                                                nsIClassInfo)

NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerEvent, nsIDOMEvent)

NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerEvent)

NS_IMETHODIMP
nsDOMWorkerEvent::GetType(nsAString& aType)
{
  aType.Assign(mType);
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerEvent::GetTarget(nsIDOMEventTarget** aTarget)
{
  NS_ENSURE_ARG_POINTER(aTarget);
  NS_IF_ADDREF(*aTarget = mTarget);
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerEvent::GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget)
{
  NS_ENSURE_ARG_POINTER(aCurrentTarget);
  NS_IF_ADDREF(*aCurrentTarget = mTarget);
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerEvent::GetEventPhase(PRUint16* aEventPhase)
{
  NS_ENSURE_ARG_POINTER(aEventPhase);
  *aEventPhase = mEventPhase;
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerEvent::GetBubbles(PRBool* aBubbles)
{
  NS_ENSURE_ARG_POINTER(aBubbles);
  *aBubbles = mBubbles;
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerEvent::GetCancelable(PRBool* aCancelable)
{
  NS_ENSURE_ARG_POINTER(aCancelable);
  *aCancelable = mCancelable;
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerEvent::GetTimeStamp(DOMTimeStamp* aTimeStamp)
{
  NS_ENSURE_ARG_POINTER(aTimeStamp);
  *aTimeStamp = mTimeStamp;
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerEvent::StopPropagation()
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsDOMWorkerEvent::PreventDefault()
{
  mPreventDefaultCalled = PR_TRUE;
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerEvent::InitEvent(const nsAString& aEventTypeArg,
                            PRBool aCanBubbleArg,
                            PRBool aCancelableArg)
{
  NS_ENSURE_FALSE(aEventTypeArg.IsEmpty(), NS_ERROR_INVALID_ARG);

  mType.Assign(aEventTypeArg);
  mBubbles = aCanBubbleArg;
  mCancelable = aCancelableArg;
  mPreventDefaultCalled = PR_FALSE;
  mTimeStamp = PR_Now();
  return NS_OK;
}

NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerMessageEvent, nsDOMWorkerEvent,
                                                      nsIWorkerMessageEvent)

NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerMessageEvent, nsIDOMEvent,
                                                      nsIWorkerMessageEvent)

NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerMessageEvent)

NS_IMETHODIMP
nsDOMWorkerMessageEvent::GetData(nsAString& aData)
{
  if (!mIsJSON) {
    aData.Assign(mData);
    return NS_OK;
  }

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

  nsAXPCNativeCallContext* cc;
  nsresult rv = xpc->GetCurrentNativeCallContext(&cc);
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_TRUE(cc, NS_ERROR_UNEXPECTED);

  jsval* retval;
  rv = cc->GetRetValPtr(&retval);
  NS_ENSURE_SUCCESS(rv, rv);

  if (mHaveCachedJSVal) {
    cc->SetReturnValueWasSet(PR_TRUE);
    *retval = mCachedJSVal;
    return NS_OK;
  }

  if (mHaveAttemptedConversion) {
    // Don't try to convert again if the first time around we saw an error.
    return NS_ERROR_FAILURE;
  }
  mHaveAttemptedConversion = PR_TRUE;

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

  JSAutoRequest ar(cx);

  JSBool ok = mCachedJSVal.Hold(cx);
  NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);

  JSONParser* parser = JS_BeginJSONParse(cx, mCachedJSVal.ToJSValPtr());
  NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);

  // This is slightly sneaky, but now that JS_BeginJSONParse succeeded we always
  // need call JS_FinishJSONParse even if JS_ConsumeJSONText fails. We'll report
  // an error if either failed, though.
  ok = JS_ConsumeJSONText(cx, parser, (jschar*)mData.get(),
                          (uint32)mData.Length());

  // Note the '&& ok' after the call here!
  ok = JS_FinishJSONParse(cx, parser, JSVAL_NULL) && ok;
  if (!ok) {
    mCachedJSVal = JSVAL_NULL;
    return NS_ERROR_UNEXPECTED;
  }

  NS_ASSERTION(mCachedJSVal.ToJSObject(), "Bad JSON result!");

  if (mIsPrimitive) {
    jsval primitive;

    ok = JS_GetProperty(cx, mCachedJSVal.ToJSObject(), JSON_PRIMITIVE_PROPNAME,
                        &primitive);
    if (!ok) {
      mCachedJSVal = JSVAL_NULL;
      return NS_ERROR_UNEXPECTED;
    }

    mCachedJSVal = primitive;
  }

  // We no longer need to hold this copy of the data around.
  mData.Truncate();

  // Now that everything has succeeded we'll set this flag so that we return the
  // cached jsval in the future.
  mHaveCachedJSVal = PR_TRUE;

  *retval = mCachedJSVal;
  cc->SetReturnValueWasSet(PR_TRUE);
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerMessageEvent::GetOrigin(nsAString& aOrigin)
{
  aOrigin.Assign(mOrigin);
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerMessageEvent::GetSource(nsISupports** aSource)
{
  NS_ENSURE_ARG_POINTER(aSource);
  NS_IF_ADDREF(*aSource = mSource);
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerMessageEvent::InitMessageEvent(const nsAString& aTypeArg,
                                          PRBool aCanBubbleArg,
                                          PRBool aCancelableArg,
                                          const nsAString& aDataArg,
                                          const nsAString& aOriginArg,
                                          nsISupports* aSourceArg)
{
  mData.Assign(aDataArg);
  mOrigin.Assign(aOriginArg);
  mSource = aSourceArg;
  return nsDOMWorkerEvent::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
}

NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerProgressEvent, nsDOMWorkerEvent,
                                                       nsIDOMProgressEvent)

NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerProgressEvent, nsIDOMEvent,
                                                       nsIDOMProgressEvent)

NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerProgressEvent)

NS_IMETHODIMP
nsDOMWorkerProgressEvent::GetLengthComputable(PRBool* aLengthComputable)
{
  NS_ENSURE_ARG_POINTER(aLengthComputable);
  *aLengthComputable = mLengthComputable;
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerProgressEvent::GetLoaded(PRUint64* aLoaded)
{
  NS_ENSURE_ARG_POINTER(aLoaded);
  *aLoaded = mLoaded;
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerProgressEvent::GetTotal(PRUint64* aTotal)
{
  NS_ENSURE_ARG_POINTER(aTotal);
  *aTotal = mTotal;
  return NS_OK;
}

NS_IMETHODIMP
nsDOMWorkerProgressEvent::InitProgressEvent(const nsAString_internal& aTypeArg,
                                            PRBool aCanBubbleArg,
                                            PRBool aCancelableArg,
                                            PRBool aLengthComputableArg,
                                            PRUint64 aLoadedArg,
                                            PRUint64 aTotalArg)
{
  mLengthComputable = aLengthComputableArg;
  mLoaded = aLoadedArg;
  mTotal = aTotalArg;
  return nsDOMWorkerEvent::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
}

NS_IMPL_THREADSAFE_ADDREF(nsDOMWorkerXHRState)
NS_IMPL_THREADSAFE_RELEASE(nsDOMWorkerXHRState)

nsDOMWorkerXHREvent::nsDOMWorkerXHREvent(nsDOMWorkerXHRProxy* aXHRProxy)
: mXHRProxy(aXHRProxy),
  mXHREventType(PR_UINT32_MAX),
  mChannelID(-1),
  mUploadEvent(PR_FALSE),
  mProgressEvent(PR_FALSE)
{
  NS_ASSERTION(aXHRProxy, "Can't be null!");
}

NS_IMPL_ADDREF_INHERITED(nsDOMWorkerXHREvent, nsDOMWorkerProgressEvent)
NS_IMPL_RELEASE_INHERITED(nsDOMWorkerXHREvent, nsDOMWorkerProgressEvent)

NS_INTERFACE_MAP_BEGIN(nsDOMWorkerXHREvent)
  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMProgressEvent, mProgressEvent)
NS_INTERFACE_MAP_END_INHERITING(nsDOMWorkerEvent)

NS_IMETHODIMP
nsDOMWorkerXHREvent::GetInterfaces(PRUint32* aCount,
                                   nsIID*** aArray)
{
  PRUint32 count = *aCount = mProgressEvent ? 2 : 1;

  *aArray = (nsIID**)nsMemory::Alloc(sizeof(nsIID*) * count);

  if (mProgressEvent) {
    (*aArray)[--count] =
      (nsIID*)nsMemory::Clone(&NS_GET_IID(nsIDOMProgressEvent), sizeof(nsIID));
  }

  (*aArray)[--count] =
    (nsIID *)nsMemory::Clone(&NS_GET_IID(nsIDOMEvent), sizeof(nsIID));

  NS_ASSERTION(!count, "Bad math!");
  return NS_OK;
}

nsresult
nsDOMWorkerXHREvent::Init(PRUint32 aXHREventType,
                          const nsAString& aType,
                          nsIDOMEvent* aEvent,
                          SnapshotChoice aSnapshot)
{
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
  NS_ASSERTION(aEvent, "Don't pass null here!");

  mXHREventType = aXHREventType;

  // Only set a channel id if we're not going to be run immediately.
  mChannelID = mXHRProxy->mSyncEventQueue ? -1 : mXHRProxy->ChannelID();

  mTarget = static_cast<nsDOMWorkerMessageHandler*>(mXHRProxy->mWorkerXHR);
  NS_ENSURE_TRUE(mTarget, NS_ERROR_UNEXPECTED);

  mXHRWN = mXHRProxy->mWorkerXHR->GetWrappedNative();
  NS_ENSURE_STATE(mXHRWN);

  nsCOMPtr<nsIDOMEventTarget> mainThreadTarget;
  nsresult rv = aEvent->GetTarget(getter_AddRefs(mainThreadTarget));
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_STATE(mainThreadTarget);

  nsCOMPtr<nsIXMLHttpRequestUpload> upload(do_QueryInterface(mainThreadTarget));
  if (upload) {
    mUploadEvent = PR_TRUE;
    mTarget =
      static_cast<nsDOMWorkerMessageHandler*>(mXHRProxy->mWorkerXHR->mUpload);
  }
  else {
    mUploadEvent = PR_FALSE;
    mTarget = static_cast<nsDOMWorkerMessageHandler*>(mXHRProxy->mWorkerXHR);
  }
  NS_ASSERTION(mTarget, "Null target!");

  PRBool bubbles;
  rv = aEvent->GetBubbles(&bubbles);
  NS_ENSURE_SUCCESS(rv, rv);

  PRBool cancelable;
  rv = aEvent->GetCancelable(&cancelable);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aEvent->GetTimeStamp(&mTimeStamp);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aEvent->GetEventPhase(&mEventPhase);
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ASSERTION(mEventPhase == nsIDOMEvent::AT_TARGET, "Unsupported phase!");

  nsCOMPtr<nsIDOMProgressEvent> progressEvent(do_QueryInterface(aEvent));
  if (progressEvent) {
    mProgressEvent = PR_TRUE;

    PRBool lengthComputable;
    rv = progressEvent->GetLengthComputable(&lengthComputable);
    NS_ENSURE_SUCCESS(rv, rv);

    PRUint64 loaded;
    rv = progressEvent->GetLoaded(&loaded);
    NS_ENSURE_SUCCESS(rv, rv);

    PRUint64 total;
    rv = progressEvent->GetTotal(&total);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = InitProgressEvent(aType, bubbles, cancelable, lengthComputable, loaded,
                           total);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  else {
    mProgressEvent = PR_FALSE;

    rv = InitEvent(aType, bubbles, cancelable);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  mState = new nsDOMWorkerXHRState();
  NS_ENSURE_TRUE(mState, NS_ERROR_OUT_OF_MEMORY);

  if (aSnapshot == SNAPSHOT) {
    SnapshotXHRState(mXHRProxy->mXHR, mState);
  }

  return NS_OK;
}

/* static */
void
nsDOMWorkerXHREvent::SnapshotXHRState(nsIXMLHttpRequest* aXHR,
                                      nsDOMWorkerXHRState* aState)
{
  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
  NS_ASSERTION(aXHR && aState, "Don't pass null here!");

  aState->responseTextResult = aXHR->GetResponseText(aState->responseText);
  aState->statusTextResult = aXHR->GetStatusText(aState->statusText);
  aState->statusResult = aXHR->GetStatus(&aState->status);
  aState->readyStateResult = aXHR->GetReadyState(&aState->readyState);
}

NS_IMETHODIMP
nsDOMWorkerXHREvent::Run()
{
  nsresult rv = mXHRProxy->HandleWorkerEvent(this, mUploadEvent);

  // Prevent reference cycles by releasing this here.
  mXHRProxy = nsnull;

  NS_ENSURE_SUCCESS(rv, rv);
  return NS_OK;
}

NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerErrorEvent, nsDOMWorkerEvent,
                                                    nsIWorkerErrorEvent)

NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerErrorEvent, nsIDOMEvent,
                                                    nsIWorkerErrorEvent)

NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerErrorEvent)

nsresult
nsDOMWorkerErrorEvent::GetMessage(nsAString& aMessage)
{
  aMessage.Assign(mMessage);
  return NS_OK;
}

nsresult
nsDOMWorkerErrorEvent::GetFilename(nsAString& aFilename)
{
  aFilename.Assign(mFilename);
  return NS_OK;
}

nsresult
nsDOMWorkerErrorEvent::GetLineno(PRUint32* aLineno)
{
  NS_ENSURE_ARG_POINTER(aLineno);
  *aLineno = mLineno;
  return NS_OK;
}

nsresult
nsDOMWorkerErrorEvent::InitErrorEvent(const nsAString& aTypeArg,
                                      PRBool aCanBubbleArg,
                                      PRBool aCancelableArg,
                                      const nsAString& aMessageArg,
                                      const nsAString& aFilenameArg,
                                      PRUint32 aLinenoArg)
{
  mMessage.Assign(aMessageArg);
  mFilename.Assign(aFilenameArg);
  mLineno = aLinenoArg;
  return InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
}
