gecko/dom/workers/XMLHttpRequest.cpp
Boris Zbarsky 56133baee1 Bug 768537 part 1. Update parser support for dictionaries to spec changes. r=jlebar
There are several parts here:

1)  Enforce the requirement that dictionary arguments not followed by a required argument are optional.
2)  Make dictionaries no longer be distinguishable from nullable types.
3)  Disallow dictionaries or unions containing dictionaries inside a nullable type.
4)  Force optional dictionaries to have a default value of null so that codegen doesn't have to worry about dealing with
    optional arguments that have no default value in the IDL but need to be treated as if they were null.
2012-07-17 12:18:53 -04:00

2195 lines
55 KiB
C++

/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* 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 "XMLHttpRequest.h"
#include "nsIDOMEvent.h"
#include "nsIDOMEventListener.h"
#include "nsIDOMProgressEvent.h"
#include "nsIRunnable.h"
#include "nsIVariant.h"
#include "nsIXMLHttpRequest.h"
#include "nsIXPConnect.h"
#include "jsfriendapi.h"
#include "nsContentUtils.h"
#include "nsJSUtils.h"
#include "nsThreadUtils.h"
#include "nsXMLHttpRequest.h"
#include "Events.h"
#include "EventTarget.h"
#include "Exceptions.h"
#include "File.h"
#include "RuntimeService.h"
#include "WorkerPrivate.h"
#include "XMLHttpRequestUpload.h"
#include "DOMBindingInlines.h"
#include "mozilla/Attributes.h"
USING_WORKERS_NAMESPACE
using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult;
using mozilla::ErrorResult;
// XXX Need to figure this out...
#define UNCATCHABLE_EXCEPTION NS_ERROR_OUT_OF_MEMORY
/**
* XMLHttpRequest in workers
*
* XHR in workers is implemented by proxying calls/events/etc between the
* worker thread and an nsXMLHttpRequest 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 nsXMLHttpRequest) 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.
*/
BEGIN_WORKERS_NAMESPACE
class Proxy MOZ_FINAL : public nsIDOMEventListener
{
public:
// Read on multiple threads.
WorkerPrivate* mWorkerPrivate;
XMLHttpRequest* mXMLHttpRequestPrivate;
// Only touched on the main thread.
nsRefPtr<nsXMLHttpRequest> mXHR;
nsCOMPtr<nsIXMLHttpRequestUpload> mXHRUpload;
PRUint32 mInnerEventStreamId;
PRUint32 mInnerChannelId;
PRUint32 mOutstandingSendCount;
// Only touched on the worker thread.
PRUint32 mOuterEventStreamId;
PRUint32 mOuterChannelId;
PRUint64 mLastLoaded;
PRUint64 mLastTotal;
PRUint64 mLastUploadLoaded;
PRUint64 mLastUploadTotal;
bool mIsSyncXHR;
bool mLastLengthComputable;
bool mLastUploadLengthComputable;
bool mSeenLoadStart;
bool mSeenUploadLoadStart;
// Only touched on the main thread.
PRUint32 mSyncQueueKey;
PRUint32 mSyncEventResponseSyncQueueKey;
bool mUploadEventListenersAttached;
bool mMainThreadSeenLoadStart;
bool mInOpen;
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
Proxy(XMLHttpRequest* aXHRPrivate)
: mWorkerPrivate(nsnull), mXMLHttpRequestPrivate(aXHRPrivate),
mInnerEventStreamId(0), mInnerChannelId(0), mOutstandingSendCount(0),
mOuterEventStreamId(0), mOuterChannelId(0), mLastLoaded(0), mLastTotal(0),
mLastUploadLoaded(0), mLastUploadTotal(0), mIsSyncXHR(false),
mLastLengthComputable(false), mLastUploadLengthComputable(false),
mSeenLoadStart(false), mSeenUploadLoadStart(false),
mSyncQueueKey(PR_UINT32_MAX),
mSyncEventResponseSyncQueueKey(PR_UINT32_MAX),
mUploadEventListenersAttached(false), mMainThreadSeenLoadStart(false),
mInOpen(false)
{ }
~Proxy()
{
NS_ASSERTION(!mXHR, "Still have an XHR object attached!");
NS_ASSERTION(!mXHRUpload, "Still have an XHR upload object attached!");
NS_ASSERTION(!mOutstandingSendCount, "We're dying too early!");
}
bool
Init()
{
AssertIsOnMainThread();
NS_ASSERTION(mWorkerPrivate, "Must have a worker here!");
if (!mXHR) {
mXHR = new nsXMLHttpRequest();
if (NS_FAILED(mXHR->Init(mWorkerPrivate->GetPrincipal(),
mWorkerPrivate->GetScriptContext(),
mWorkerPrivate->GetWindow(),
mWorkerPrivate->GetBaseURI()))) {
mXHR = nsnull;
return false;
}
if (NS_FAILED(mXHR->GetUpload(getter_AddRefs(mXHRUpload)))) {
mXHR = nsnull;
return false;
}
if (!AddRemoveEventListeners(false, true)) {
mXHRUpload = nsnull;
mXHR = nsnull;
return false;
}
}
return true;
}
void
Teardown();
bool
AddRemoveEventListeners(bool aUpload, bool aAdd);
void
Reset()
{
AssertIsOnMainThread();
if (mUploadEventListenersAttached) {
AddRemoveEventListeners(true, false);
}
}
PRUint32
GetSyncQueueKey()
{
AssertIsOnMainThread();
return mSyncEventResponseSyncQueueKey == PR_UINT32_MAX ?
mSyncQueueKey :
mSyncEventResponseSyncQueueKey;
}
bool
EventsBypassSyncQueue()
{
AssertIsOnMainThread();
return mSyncQueueKey == PR_UINT32_MAX &&
mSyncEventResponseSyncQueueKey == PR_UINT32_MAX;
}
};
END_WORKERS_NAMESPACE
namespace {
inline void
ConvertResponseTypeToString(XMLHttpRequestResponseType aType,
nsString& aString)
{
using namespace
mozilla::dom::XMLHttpRequestResponseTypeValues;
size_t index = static_cast<size_t>(aType);
MOZ_ASSERT(index < ArrayLength(strings), "Codegen gave us a bad value!");
aString.AssignASCII(strings[index].value, strings[index].length);
}
inline XMLHttpRequestResponseType
ConvertStringToResponseType(const nsAString& aString)
{
using namespace
mozilla::dom::XMLHttpRequestResponseTypeValues;
for (size_t index = 0; index < ArrayLength(strings) - 1; index++) {
if (aString.EqualsASCII(strings[index].value, strings[index].length)) {
return static_cast<XMLHttpRequestResponseType>(index);
}
}
MOZ_NOT_REACHED("Don't know anything about this response type!");
return _empty;
}
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
};
JS_STATIC_ASSERT(STRING_LAST_XHR >= STRING_LAST_EVENTTARGET);
JS_STATIC_ASSERT(STRING_LAST_XHR == STRING_COUNT - 1);
const char* const sEventStrings[] = {
// nsIXMLHttpRequestEventTarget event types, supported by both XHR and Upload.
"abort",
"error",
"load",
"loadstart",
"progress",
"timeout",
// nsIXMLHttpRequest event types, supported only by XHR.
"readystatechange",
"loadend",
};
JS_STATIC_ASSERT(JS_ARRAY_LENGTH(sEventStrings) == STRING_COUNT);
class MainThreadSyncRunnable : public WorkerSyncRunnable
{
public:
MainThreadSyncRunnable(WorkerPrivate* aWorkerPrivate,
ClearingBehavior aClearingBehavior,
PRUint32 aSyncQueueKey,
bool aBypassSyncEventQueue)
: WorkerSyncRunnable(aWorkerPrivate, aSyncQueueKey, aBypassSyncEventQueue,
aClearingBehavior)
{
AssertIsOnMainThread();
}
bool
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
AssertIsOnMainThread();
return true;
}
void
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
bool aDispatchResult)
{
AssertIsOnMainThread();
}
};
class MainThreadProxyRunnable : public MainThreadSyncRunnable
{
protected:
nsRefPtr<Proxy> mProxy;
public:
MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate,
ClearingBehavior aClearingBehavior, Proxy* aProxy)
: MainThreadSyncRunnable(aWorkerPrivate, aClearingBehavior,
aProxy->GetSyncQueueKey(),
aProxy->EventsBypassSyncQueue()),
mProxy(aProxy)
{ }
};
class XHRUnpinRunnable : public WorkerControlRunnable
{
XMLHttpRequest* mXMLHttpRequestPrivate;
public:
XHRUnpinRunnable(WorkerPrivate* aWorkerPrivate,
XMLHttpRequest* aXHRPrivate)
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
mXMLHttpRequestPrivate(aXHRPrivate)
{ }
bool
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
AssertIsOnMainThread();
return true;
}
void
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
bool aDispatchResult)
{
AssertIsOnMainThread();
}
bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
mXMLHttpRequestPrivate->Unpin();
return true;
}
};
class AsyncTeardownRunnable : public nsRunnable
{
nsRefPtr<Proxy> mProxy;
public:
AsyncTeardownRunnable(Proxy* aProxy)
{
mProxy = aProxy;
NS_ASSERTION(mProxy, "Null proxy!");
}
NS_IMETHOD Run()
{
AssertIsOnMainThread();
mProxy->Teardown();
mProxy = nsnull;
return NS_OK;
}
};
class LoadStartDetectionRunnable MOZ_FINAL : public nsIRunnable,
public nsIDOMEventListener
{
WorkerPrivate* mWorkerPrivate;
nsRefPtr<Proxy> mProxy;
nsRefPtr<nsXMLHttpRequest> mXHR;
XMLHttpRequest* mXMLHttpRequestPrivate;
nsString mEventType;
bool mReceivedLoadStart;
PRUint32 mChannelId;
class ProxyCompleteRunnable : public MainThreadProxyRunnable
{
XMLHttpRequest* mXMLHttpRequestPrivate;
PRUint32 mChannelId;
public:
ProxyCompleteRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
XMLHttpRequest* aXHRPrivate, PRUint32 aChannelId)
: MainThreadProxyRunnable(aWorkerPrivate, RunWhenClearing, aProxy),
mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(aChannelId)
{ }
bool
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
AssertIsOnMainThread();
return true;
}
void
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
bool aDispatchResult)
{
AssertIsOnMainThread();
}
bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
if (mChannelId != mProxy->mOuterChannelId) {
// Threads raced, this event is now obsolete.
return true;
}
if (mSyncQueueKey != PR_UINT32_MAX) {
aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true);
}
mXMLHttpRequestPrivate->Unpin();
return true;
}
};
public:
NS_DECL_ISUPPORTS
LoadStartDetectionRunnable(Proxy* aProxy, XMLHttpRequest* aXHRPrivate)
: mWorkerPrivate(aProxy->mWorkerPrivate), mProxy(aProxy), mXHR(aProxy->mXHR),
mXMLHttpRequestPrivate(aXHRPrivate), mReceivedLoadStart(false),
mChannelId(mProxy->mInnerChannelId)
{
AssertIsOnMainThread();
mEventType.AssignWithConversion(sEventStrings[STRING_loadstart]);
}
~LoadStartDetectionRunnable()
{
AssertIsOnMainThread();
}
bool
RegisterAndDispatch()
{
AssertIsOnMainThread();
if (NS_FAILED(mXHR->AddEventListener(mEventType, this, false, false, 2))) {
NS_WARNING("Failed to add event listener!");
return false;
}
return NS_SUCCEEDED(NS_DispatchToCurrentThread(this));
}
NS_IMETHOD
Run()
{
AssertIsOnMainThread();
if (NS_FAILED(mXHR->RemoveEventListener(mEventType, this, false))) {
NS_WARNING("Failed to remove event listener!");
}
if (!mReceivedLoadStart) {
if (mProxy->mOutstandingSendCount > 1) {
mProxy->mOutstandingSendCount--;
} else if (mProxy->mOutstandingSendCount == 1) {
mProxy->Reset();
nsRefPtr<ProxyCompleteRunnable> runnable =
new ProxyCompleteRunnable(mWorkerPrivate, mProxy,
mXMLHttpRequestPrivate,
mChannelId);
if (runnable->Dispatch(nsnull)) {
mProxy->mWorkerPrivate = nsnull;
mProxy->mOutstandingSendCount--;
}
}
}
mProxy = nsnull;
mXHR = nsnull;
mXMLHttpRequestPrivate = nsnull;
return NS_OK;
}
NS_IMETHOD
HandleEvent(nsIDOMEvent* aEvent)
{
AssertIsOnMainThread();
#ifdef DEBUG
{
nsString type;
if (NS_SUCCEEDED(aEvent->GetType(type))) {
NS_ASSERTION(type == mEventType, "Unexpected event type!");
}
else {
NS_WARNING("Failed to get event type!");
}
}
#endif
mReceivedLoadStart = true;
return NS_OK;
}
};
NS_IMPL_ISUPPORTS2(LoadStartDetectionRunnable, nsIRunnable, nsIDOMEventListener)
class EventRunnable : public MainThreadProxyRunnable
{
nsString mType;
nsString mResponseType;
JSAutoStructuredCloneBuffer mResponseBuffer;
nsTArray<nsCOMPtr<nsISupports> > mClonedObjects;
jsval mResponse;
nsString mResponseText;
nsString mStatusText;
PRUint64 mLoaded;
PRUint64 mTotal;
PRUint32 mEventStreamId;
PRUint32 mStatus;
PRUint16 mReadyState;
bool mUploadEvent;
bool mProgressEvent;
bool mLengthComputable;
nsresult mResponseTextResult;
nsresult mStatusResult;
nsresult mResponseResult;
public:
EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
bool aLengthComputable, PRUint64 aLoaded, PRUint64 aTotal)
: MainThreadProxyRunnable(aProxy->mWorkerPrivate, SkipWhenClearing, aProxy),
mType(aType), mResponse(JSVAL_VOID), mLoaded(aLoaded), mTotal(aTotal),
mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0),
mUploadEvent(aUploadEvent), mProgressEvent(true),
mLengthComputable(aLengthComputable), mResponseTextResult(NS_OK),
mStatusResult(NS_OK), mResponseResult(NS_OK)
{ }
EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType)
: MainThreadProxyRunnable(aProxy->mWorkerPrivate, SkipWhenClearing, aProxy),
mType(aType), mResponse(JSVAL_VOID), mLoaded(0), mTotal(0),
mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0),
mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0),
mResponseTextResult(NS_OK), mStatusResult(NS_OK), mResponseResult(NS_OK)
{ }
bool
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
nsRefPtr<nsXMLHttpRequest>& xhr = mProxy->mXHR;
NS_ASSERTION(xhr, "Must have an XHR here!");
if (NS_FAILED(xhr->GetResponseType(mResponseType))) {
NS_ERROR("This should never fail!");
}
mResponseTextResult = xhr->GetResponseText(mResponseText);
if (NS_SUCCEEDED(mResponseTextResult)) {
mResponseResult = mResponseTextResult;
if (mResponseText.IsVoid()) {
mResponse = JSVAL_NULL;
}
}
else {
jsval response;
mResponseResult = xhr->GetResponse(aCx, &response);
if (NS_SUCCEEDED(mResponseResult)) {
if (JSVAL_IS_UNIVERSAL(response)) {
mResponse = response;
}
else {
// Anything subject to GC must be cloned.
JSStructuredCloneCallbacks* callbacks =
aWorkerPrivate->IsChromeWorker() ?
ChromeWorkerStructuredCloneCallbacks(true) :
WorkerStructuredCloneCallbacks(true);
nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
if (mResponseBuffer.write(aCx, response, callbacks, &clonedObjects)) {
mClonedObjects.SwapElements(clonedObjects);
}
else {
NS_WARNING("Failed to clone response!");
mResponseResult = NS_ERROR_DOM_DATA_CLONE_ERR;
}
}
}
}
mStatusResult = xhr->GetStatus(&mStatus);
xhr->GetStatusText(mStatusText);
mReadyState = xhr->GetReadyState();
return true;
}
bool
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 {
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;
}
}
else if (mType.EqualsASCII(sEventStrings[STRING_readystatechange])) {
if (mReadyState == 4 && !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;
}
}
XMLHttpRequest::StateData state;
state.mResponseTextResult = mResponseTextResult;
state.mResponseText = mResponseText;
if (NS_SUCCEEDED(mResponseTextResult)) {
MOZ_ASSERT(JSVAL_IS_VOID(mResponse) || JSVAL_IS_NULL(mResponse));
state.mResponseResult = mResponseTextResult;
state.mResponse = mResponse;
}
else {
state.mResponseResult = mResponseResult;
if (NS_SUCCEEDED(mResponseResult)) {
if (mResponseBuffer.data()) {
MOZ_ASSERT(JSVAL_IS_VOID(mResponse));
JSAutoStructuredCloneBuffer responseBuffer;
mResponseBuffer.swap(responseBuffer);
JSStructuredCloneCallbacks* callbacks =
aWorkerPrivate->IsChromeWorker() ?
ChromeWorkerStructuredCloneCallbacks(false) :
WorkerStructuredCloneCallbacks(false);
nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
clonedObjects.SwapElements(mClonedObjects);
jsval response;
if (!responseBuffer.read(aCx, &response, callbacks, &clonedObjects)) {
return false;
}
state.mResponse = response;
}
else {
state.mResponse = mResponse;
}
}
}
state.mStatusResult = mStatusResult;
state.mStatus = mStatus;
state.mStatusText = mStatusText;
state.mReadyState = mReadyState;
XMLHttpRequest* xhr = mProxy->mXMLHttpRequestPrivate;
xhr->UpdateState(state);
if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) {
return true;
}
JSString* type = JS_NewUCStringCopyN(aCx, mType.get(), mType.Length());
if (!type) {
return false;
}
JSObject* event = mProgressEvent ?
events::CreateProgressEvent(aCx, type, mLengthComputable,
mLoaded, mTotal) :
events::CreateGenericEvent(aCx, type, false, false,
false);
if (!event) {
return false;
}
JSObject* target = mUploadEvent ?
xhr->GetUploadObjectNoCreate()->GetJSObject() :
xhr->GetJSObject();
MOZ_ASSERT(target);
bool dummy;
if (!events::DispatchEventToTarget(aCx, target, event, &dummy)) {
JS_ReportPendingException(aCx);
}
// After firing the event set mResponse to JSVAL_NULL for chunked response
// types.
if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) {
xhr->NullResponseText();
}
return true;
}
};
class WorkerThreadProxySyncRunnable : public nsRunnable
{
protected:
WorkerPrivate* mWorkerPrivate;
nsRefPtr<Proxy> mProxy;
PRUint32 mSyncQueueKey;
private:
class ResponseRunnable : public MainThreadProxyRunnable
{
PRUint32 mSyncQueueKey;
nsresult mErrorCode;
public:
ResponseRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
PRUint32 aSyncQueueKey, nsresult aErrorCode)
: MainThreadProxyRunnable(aWorkerPrivate, SkipWhenClearing, aProxy),
mSyncQueueKey(aSyncQueueKey), mErrorCode(aErrorCode)
{
NS_ASSERTION(aProxy, "Don't hand me a null proxy!");
}
bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
if (NS_FAILED(mErrorCode)) {
ThrowDOMExceptionForNSResult(aCx, mErrorCode);
aWorkerPrivate->StopSyncLoop(mSyncQueueKey, false);
}
else {
aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true);
}
return true;
}
};
public:
WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
: mWorkerPrivate(aWorkerPrivate), mProxy(aProxy), mSyncQueueKey(0)
{
mWorkerPrivate->AssertIsOnWorkerThread();
NS_ASSERTION(aProxy, "Don't hand me a null proxy!");
}
bool
Dispatch(JSContext* aCx)
{
mWorkerPrivate->AssertIsOnWorkerThread();
mSyncQueueKey = mWorkerPrivate->CreateNewSyncLoop();
if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
JS_ReportError(aCx, "Failed to dispatch to main thread!");
return false;
}
if (!mWorkerPrivate->RunSyncLoop(aCx, mSyncQueueKey)) {
return false;
}
return true;
}
virtual nsresult
MainThreadRun() = 0;
NS_IMETHOD
Run()
{
AssertIsOnMainThread();
PRUint32 oldSyncQueueKey = mProxy->mSyncEventResponseSyncQueueKey;
mProxy->mSyncEventResponseSyncQueueKey = mSyncQueueKey;
nsresult rv = MainThreadRun();
nsRefPtr<ResponseRunnable> response =
new ResponseRunnable(mWorkerPrivate, mProxy, mSyncQueueKey, rv);
if (!response->Dispatch(nsnull)) {
NS_WARNING("Failed to dispatch response!");
}
mProxy->mSyncEventResponseSyncQueueKey = oldSyncQueueKey;
return NS_OK;
}
};
class SyncTeardownRunnable : public WorkerThreadProxySyncRunnable
{
public:
SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
{
MOZ_ASSERT(aWorkerPrivate);
MOZ_ASSERT(aProxy);
}
virtual nsresult
MainThreadRun()
{
AssertIsOnMainThread();
mProxy->Teardown();
return NS_OK;
}
};
class SetMultipartRunnable : public WorkerThreadProxySyncRunnable
{
bool mValue;
public:
SetMultipartRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
bool aValue)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mValue(aValue)
{ }
nsresult
MainThreadRun()
{
return mProxy->mXHR->SetMultipart(mValue);
}
};
class SetBackgroundRequestRunnable : public WorkerThreadProxySyncRunnable
{
bool mValue;
public:
SetBackgroundRequestRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
bool aValue)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mValue(aValue)
{ }
nsresult
MainThreadRun()
{
return mProxy->mXHR->SetMozBackgroundRequest(mValue);
}
};
class SetWithCredentialsRunnable : public WorkerThreadProxySyncRunnable
{
bool mValue;
public:
SetWithCredentialsRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
bool aValue)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mValue(aValue)
{ }
nsresult
MainThreadRun()
{
return mProxy->mXHR->SetWithCredentials(mValue);
}
};
class SetResponseTypeRunnable : public WorkerThreadProxySyncRunnable
{
nsString mResponseType;
public:
SetResponseTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
const nsAString& aResponseType)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
mResponseType(aResponseType)
{ }
nsresult
MainThreadRun()
{
nsresult rv = mProxy->mXHR->SetResponseType(mResponseType);
mResponseType.Truncate();
if (NS_SUCCEEDED(rv)) {
rv = mProxy->mXHR->GetResponseType(mResponseType);
}
return rv;
}
void
GetResponseType(nsAString& aResponseType) {
aResponseType.Assign(mResponseType);
}
};
class SetTimeoutRunnable : public WorkerThreadProxySyncRunnable
{
PRUint32 mTimeout;
public:
SetTimeoutRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
PRUint32 aTimeout)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
mTimeout(aTimeout)
{ }
nsresult
MainThreadRun()
{
return mProxy->mXHR->SetTimeout(mTimeout);
}
};
class AbortRunnable : public WorkerThreadProxySyncRunnable
{
public:
AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
{ }
nsresult
MainThreadRun()
{
mProxy->mInnerEventStreamId++;
WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
mProxy->mWorkerPrivate = mWorkerPrivate;
mProxy->mXHR->Abort();
mProxy->mWorkerPrivate = oldWorker;
mProxy->Reset();
return NS_OK;
}
};
class GetAllResponseHeadersRunnable : public WorkerThreadProxySyncRunnable
{
nsString& mResponseHeaders;
public:
GetAllResponseHeadersRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
nsString& aResponseHeaders)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
mResponseHeaders(aResponseHeaders)
{ }
nsresult
MainThreadRun()
{
mProxy->mXHR->GetAllResponseHeaders(mResponseHeaders);
return NS_OK;
}
};
class GetResponseHeaderRunnable : public WorkerThreadProxySyncRunnable
{
const nsCString mHeader;
nsCString& mValue;
public:
GetResponseHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
const nsCString& aHeader, nsCString& aValue)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mHeader(aHeader),
mValue(aValue)
{ }
nsresult
MainThreadRun()
{
return mProxy->mXHR->GetResponseHeader(mHeader, mValue);
}
};
class OpenRunnable : public WorkerThreadProxySyncRunnable
{
nsString mMethod;
nsString mURL;
Optional<nsAString> mUser;
nsString mUserStr;
Optional<nsAString> mPassword;
nsString mPasswordStr;
bool mMultipart;
bool mBackgroundRequest;
bool mWithCredentials;
PRUint32 mTimeout;
public:
OpenRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
const nsAString& aMethod, const nsAString& aURL,
const Optional<nsAString>& aUser,
const Optional<nsAString>& aPassword,
bool aMultipart, bool aBackgroundRequest, bool aWithCredentials,
PRUint32 aTimeout)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMethod(aMethod),
mURL(aURL), mMultipart(aMultipart),
mBackgroundRequest(aBackgroundRequest), mWithCredentials(aWithCredentials),
mTimeout(aTimeout)
{
if (aUser.WasPassed()) {
mUserStr = aUser.Value();
mUser = &mUserStr;
}
if (aPassword.WasPassed()) {
mPasswordStr = aPassword.Value();
mPassword = &mPasswordStr;
}
}
nsresult
MainThreadRun()
{
WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
mProxy->mWorkerPrivate = mWorkerPrivate;
nsresult rv = MainThreadRunInternal();
mProxy->mWorkerPrivate = oldWorker;
return rv;
}
nsresult
MainThreadRunInternal()
{
if (!mProxy->Init()) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
nsresult rv;
if (mMultipart) {
rv = mProxy->mXHR->SetMultipart(mMultipart);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mBackgroundRequest) {
rv = mProxy->mXHR->SetMozBackgroundRequest(mBackgroundRequest);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mWithCredentials) {
rv = mProxy->mXHR->SetWithCredentials(mWithCredentials);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mTimeout) {
rv = mProxy->mXHR->SetTimeout(mTimeout);
NS_ENSURE_SUCCESS(rv, rv);
}
NS_ASSERTION(!mProxy->mInOpen, "Reentrancy is bad!");
mProxy->mInOpen = true;
ErrorResult rv2;
mProxy->mXHR->Open(mMethod, mURL, true, mUser, mPassword, rv2);
NS_ASSERTION(mProxy->mInOpen, "Reentrancy is bad!");
mProxy->mInOpen = false;
if (rv2.Failed()) {
return rv2.ErrorCode();
}
rv = mProxy->mXHR->SetResponseType(NS_LITERAL_STRING("text"));
return rv;
}
};
class SendRunnable : public WorkerThreadProxySyncRunnable
{
nsString mStringBody;
JSAutoStructuredCloneBuffer mBody;
nsTArray<nsCOMPtr<nsISupports> > mClonedObjects;
PRUint32 mSyncQueueKey;
bool mHasUploadListeners;
public:
SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
const nsAString& aStringBody, JSAutoStructuredCloneBuffer& aBody,
nsTArray<nsCOMPtr<nsISupports> >& aClonedObjects,
PRUint32 aSyncQueueKey, bool aHasUploadListeners)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
mStringBody(aStringBody), mSyncQueueKey(aSyncQueueKey),
mHasUploadListeners(aHasUploadListeners)
{
mBody.swap(aBody);
mClonedObjects.SwapElements(aClonedObjects);
}
nsresult
MainThreadRun()
{
nsCOMPtr<nsIVariant> variant;
if (mBody.data()) {
RuntimeService::AutoSafeJSContext cx;
nsIXPConnect* xpc = nsContentUtils::XPConnect();
NS_ASSERTION(xpc, "This should never be null!");
nsresult rv = NS_OK;
JSStructuredCloneCallbacks* callbacks =
mWorkerPrivate->IsChromeWorker() ?
ChromeWorkerStructuredCloneCallbacks(true) :
WorkerStructuredCloneCallbacks(true);
jsval body;
if (mBody.read(cx, &body, callbacks, &mClonedObjects)) {
if (NS_FAILED(xpc->JSValToVariant(cx, &body,
getter_AddRefs(variant)))) {
rv = NS_ERROR_DOM_INVALID_STATE_ERR;
}
}
else {
rv = NS_ERROR_DOM_DATA_CLONE_ERR;
}
mBody.clear();
mClonedObjects.Clear();
NS_ENSURE_SUCCESS(rv, rv);
}
else {
nsCOMPtr<nsIWritableVariant> wvariant =
do_CreateInstance(NS_VARIANT_CONTRACTID);
NS_ENSURE_TRUE(wvariant, NS_ERROR_UNEXPECTED);
if (NS_FAILED(wvariant->SetAsAString(mStringBody))) {
NS_ERROR("This should never fail!");
}
variant = wvariant;
}
NS_ASSERTION(!mProxy->mWorkerPrivate, "Should be null!");
mProxy->mWorkerPrivate = mWorkerPrivate;
NS_ASSERTION(mProxy->mSyncQueueKey == PR_UINT32_MAX, "Should be unset!");
mProxy->mSyncQueueKey = mSyncQueueKey;
if (mHasUploadListeners) {
NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!");
if (!mProxy->AddRemoveEventListeners(true, true)) {
NS_ERROR("This should never fail!");
}
}
mProxy->mInnerChannelId++;
nsresult rv = mProxy->mXHR->Send(variant);
if (NS_SUCCEEDED(rv)) {
mProxy->mOutstandingSendCount++;
if (!mHasUploadListeners) {
NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!");
if (!mProxy->AddRemoveEventListeners(true, true)) {
NS_ERROR("This should never fail!");
}
}
}
return rv;
}
};
class SetRequestHeaderRunnable : public WorkerThreadProxySyncRunnable
{
nsCString mHeader;
nsCString mValue;
public:
SetRequestHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
const nsCString& aHeader, const nsCString& aValue)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mHeader(aHeader),
mValue(aValue)
{ }
nsresult
MainThreadRun()
{
return mProxy->mXHR->SetRequestHeader(mHeader, mValue);
}
};
class OverrideMimeTypeRunnable : public WorkerThreadProxySyncRunnable
{
nsString mMimeType;
public:
OverrideMimeTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
const nsAString& aMimeType)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMimeType(aMimeType)
{ }
nsresult
MainThreadRun()
{
mProxy->mXHR->OverrideMimeType(mMimeType);
return NS_OK;
}
};
class AutoUnpinXHR
{
public:
AutoUnpinXHR(XMLHttpRequest* aXMLHttpRequestPrivate)
: mXMLHttpRequestPrivate(aXMLHttpRequestPrivate)
{
MOZ_ASSERT(aXMLHttpRequestPrivate);
}
~AutoUnpinXHR()
{
if (mXMLHttpRequestPrivate) {
mXMLHttpRequestPrivate->Unpin();
}
}
void Clear()
{
mXMLHttpRequestPrivate = NULL;
}
private:
XMLHttpRequest* mXMLHttpRequestPrivate;
};
} // anonymous namespace
void
Proxy::Teardown()
{
AssertIsOnMainThread();
if (mXHR) {
Reset();
// NB: We are intentionally dropping events coming from xhr.abort on the
// floor.
AddRemoveEventListeners(false, false);
mXHR->Abort();
if (mOutstandingSendCount) {
nsRefPtr<XHRUnpinRunnable> runnable =
new XHRUnpinRunnable(mWorkerPrivate, mXMLHttpRequestPrivate);
if (!runnable->Dispatch(nsnull)) {
NS_RUNTIMEABORT("We're going to hang at shutdown anyways.");
}
mWorkerPrivate = nsnull;
mOutstandingSendCount = 0;
}
mXHRUpload = nsnull;
mXHR = nsnull;
}
}
bool
Proxy::AddRemoveEventListeners(bool aUpload, bool aAdd)
{
AssertIsOnMainThread();
NS_ASSERTION(!aUpload ||
(mUploadEventListenersAttached && !aAdd) ||
(!mUploadEventListenersAttached && aAdd),
"Messed up logic for upload listeners!");
nsCOMPtr<nsIDOMEventTarget> target =
aUpload ?
do_QueryInterface(mXHRUpload) :
do_QueryInterface(static_cast<nsIXMLHttpRequest*>(mXHR.get()));
NS_ASSERTION(target, "This should never fail!");
PRUint32 lastEventType = aUpload ? STRING_LAST_EVENTTARGET : STRING_LAST_XHR;
nsAutoString eventType;
for (PRUint32 index = 0; index <= lastEventType; index++) {
eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]);
if (aAdd) {
if (NS_FAILED(target->AddEventListener(eventType, this, false))) {
return false;
}
}
else if (NS_FAILED(target->RemoveEventListener(eventType, this, false))) {
return false;
}
}
if (aUpload) {
mUploadEventListenersAttached = aAdd;
}
return true;
}
NS_IMPL_THREADSAFE_ISUPPORTS1(Proxy, nsIDOMEventListener)
NS_IMETHODIMP
Proxy::HandleEvent(nsIDOMEvent* aEvent)
{
AssertIsOnMainThread();
if (!mWorkerPrivate || !mXMLHttpRequestPrivate) {
NS_ERROR("Shouldn't get here!");
return NS_OK;
}
nsString type;
if (NS_FAILED(aEvent->GetType(type))) {
NS_WARNING("Failed to get event type!");
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDOMEventTarget> target;
if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target)))) {
NS_WARNING("Failed to get target!");
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIXMLHttpRequestUpload> uploadTarget = do_QueryInterface(target);
nsCOMPtr<nsIDOMProgressEvent> progressEvent = do_QueryInterface(aEvent);
nsRefPtr<EventRunnable> runnable;
if (mInOpen && type.EqualsASCII(sEventStrings[STRING_readystatechange])) {
PRUint16 readyState = 0;
if (NS_SUCCEEDED(mXHR->GetReadyState(&readyState)) &&
readyState == nsIXMLHttpRequest::OPENED) {
mInnerEventStreamId++;
}
}
if (progressEvent) {
bool lengthComputable;
PRUint64 loaded, total;
if (NS_FAILED(progressEvent->GetLengthComputable(&lengthComputable)) ||
NS_FAILED(progressEvent->GetLoaded(&loaded)) ||
NS_FAILED(progressEvent->GetTotal(&total))) {
NS_WARNING("Bad progress event!");
return NS_ERROR_FAILURE;
}
runnable = new EventRunnable(this, !!uploadTarget, type, lengthComputable,
loaded, total);
}
else {
runnable = new EventRunnable(this, !!uploadTarget, type);
}
{
RuntimeService::AutoSafeJSContext cx;
runnable->Dispatch(cx);
}
if (!uploadTarget) {
if (type.EqualsASCII(sEventStrings[STRING_loadstart])) {
NS_ASSERTION(!mMainThreadSeenLoadStart, "Huh?!");
mMainThreadSeenLoadStart = true;
}
else if (mMainThreadSeenLoadStart &&
type.EqualsASCII(sEventStrings[STRING_loadend])) {
mMainThreadSeenLoadStart = false;
nsRefPtr<LoadStartDetectionRunnable> runnable =
new LoadStartDetectionRunnable(this, mXMLHttpRequestPrivate);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
NS_WARNING("Failed to dispatch LoadStartDetectionRunnable!");
}
}
}
return NS_OK;
}
XMLHttpRequest::XMLHttpRequest(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
: XMLHttpRequestEventTarget(aCx), mJSObject(NULL), mUpload(NULL),
mWorkerPrivate(aWorkerPrivate),
mResponseType(XMLHttpRequestResponseTypeValues::Text), mTimeout(0),
mJSObjectRooted(false), mMultipart(false), mBackgroundRequest(false),
mWithCredentials(false), mCanceled(false)
{
mWorkerPrivate->AssertIsOnWorkerThread();
}
XMLHttpRequest::~XMLHttpRequest()
{
mWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(!mJSObjectRooted);
}
void
XMLHttpRequest::_trace(JSTracer* aTrc)
{
if (mUpload) {
JS_CALL_OBJECT_TRACER(aTrc, mUpload->GetJSObject(), "mUpload");
}
JS_CALL_VALUE_TRACER(aTrc, mStateData.mResponse, "mResponse");
XMLHttpRequestEventTarget::_trace(aTrc);
}
void
XMLHttpRequest::_finalize(JSFreeOp* aFop)
{
ReleaseProxy(XHRIsGoingAway);
XMLHttpRequestEventTarget::_finalize(aFop);
}
// static
XMLHttpRequest*
XMLHttpRequest::Constructor(JSContext* aCx,
JSObject* aGlobal,
const MozXMLHttpRequestParametersWorkers& aParams,
ErrorResult& aRv)
{
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
MOZ_ASSERT(workerPrivate);
nsRefPtr<XMLHttpRequest> xhr = new XMLHttpRequest(aCx, workerPrivate);
if (!Wrap(aCx, aGlobal, xhr)) {
aRv.Throw(NS_ERROR_FAILURE);
return NULL;
}
// TODO: process aParams. See bug 761227
xhr->mJSObject = xhr->GetJSObject();
return xhr;
}
void
XMLHttpRequest::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).
nsRefPtr<AsyncTeardownRunnable> runnable =
new AsyncTeardownRunnable(mProxy);
mProxy = nsnull;
if (NS_DispatchToMainThread(runnable)) {
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.
nsRefPtr<SyncTeardownRunnable> runnable =
new SyncTeardownRunnable(mWorkerPrivate, mProxy);
mProxy = nsnull;
if (!runnable->Dispatch(nsnull)) {
NS_ERROR("Failed to dispatch teardown runnable!");
}
}
}
}
void
XMLHttpRequest::MaybePin(ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mJSObjectRooted) {
return;
}
JSContext* cx = GetJSContext();
if (!JS_AddNamedObjectRoot(cx, &mJSObject, "XMLHttpRequest mJSObject")) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
if (!mWorkerPrivate->AddFeature(cx, this)) {
JS_RemoveObjectRoot(cx, &mJSObject);
aRv.Throw(NS_ERROR_FAILURE);
return;
}
mJSObjectRooted = true;
}
void
XMLHttpRequest::MaybeDispatchPrematureAbortEvents(ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(mProxy);
mStateData.mReadyState = 4;
if (mProxy->mSeenUploadLoadStart) {
MOZ_ASSERT(mUpload);
JSObject* target = mUpload->GetJSObject();
MOZ_ASSERT(target);
DispatchPrematureAbortEvent(target, STRING_abort, true, aRv);
if (aRv.Failed()) {
return;
}
DispatchPrematureAbortEvent(target, STRING_loadend, true, aRv);
if (aRv.Failed()) {
return;
}
mProxy->mSeenUploadLoadStart = false;
}
if (mProxy->mSeenLoadStart) {
JSObject* target = GetJSObject();
MOZ_ASSERT(target);
DispatchPrematureAbortEvent(target, STRING_readystatechange, false, aRv);
if (aRv.Failed()) {
return;
}
DispatchPrematureAbortEvent(target, STRING_abort, false, aRv);
if (aRv.Failed()) {
return;
}
DispatchPrematureAbortEvent(target, STRING_loadend, false, aRv);
if (aRv.Failed()) {
return;
}
mProxy->mSeenLoadStart = false;
}
}
void
XMLHttpRequest::DispatchPrematureAbortEvent(JSObject* aTarget,
uint8_t aEventType,
bool aUploadTarget,
ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(mProxy);
MOZ_ASSERT(aTarget);
MOZ_ASSERT(aEventType <= STRING_COUNT);
JSContext* cx = GetJSContext();
JSString* type = JS_NewStringCopyZ(cx, sEventStrings[aEventType]);
if (!type) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
JSObject* event;
if (aEventType == STRING_readystatechange) {
event = events::CreateGenericEvent(cx, type, false, false, false);
}
else if (aUploadTarget) {
event = events::CreateProgressEvent(cx, type,
mProxy->mLastUploadLengthComputable,
mProxy->mLastUploadLoaded,
mProxy->mLastUploadTotal);
}
else {
event = events::CreateProgressEvent(cx, type,
mProxy->mLastLengthComputable,
mProxy->mLastLoaded,
mProxy->mLastTotal);
}
if (!event) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
bool dummy;
if (!events::DispatchEventToTarget(cx, aTarget, event, &dummy)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
}
void
XMLHttpRequest::Unpin()
{
mWorkerPrivate->AssertIsOnWorkerThread();
NS_ASSERTION(mJSObjectRooted, "Mismatched calls to Unpin!");
JSContext* cx = GetJSContext();
JS_RemoveObjectRoot(cx, &mJSObject);
mWorkerPrivate->RemoveFeature(cx, this);
mJSObjectRooted = false;
}
void
XMLHttpRequest::SendInternal(const nsAString& aStringBody,
JSAutoStructuredCloneBuffer& aBody,
nsTArray<nsCOMPtr<nsISupports> >& aClonedObjects,
ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
bool hasUploadListeners = mUpload ? mUpload->HasListeners() : false;
MaybePin(aRv);
if (aRv.Failed()) {
return;
}
AutoUnpinXHR autoUnpin(this);
PRUint32 syncQueueKey = PR_UINT32_MAX;
if (mProxy->mIsSyncXHR) {
syncQueueKey = mWorkerPrivate->CreateNewSyncLoop();
}
mProxy->mOuterChannelId++;
JSContext* cx = GetJSContext();
nsRefPtr<SendRunnable> runnable =
new SendRunnable(mWorkerPrivate, mProxy, aStringBody, aBody,
aClonedObjects, syncQueueKey, hasUploadListeners);
if (!runnable->Dispatch(cx)) {
return;
}
autoUnpin.Clear();
// The event loop was spun above, make sure we aren't canceled already.
if (mCanceled) {
return;
}
if (mProxy->mIsSyncXHR && !mWorkerPrivate->RunSyncLoop(cx, syncQueueKey)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
}
bool
XMLHttpRequest::Notify(JSContext* aCx, Status aStatus)
{
mWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(GetJSContext() == aCx);
if (aStatus >= Canceling && !mCanceled) {
mCanceled = true;
ReleaseProxy(WorkerIsGoingAway);
}
return true;
}
void
XMLHttpRequest::Open(const nsAString& aMethod, const nsAString& aUrl,
bool aAsync, const Optional<nsAString>& aUser,
const Optional<nsAString>& aPassword, ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
if (mProxy) {
MaybeDispatchPrematureAbortEvents(aRv);
if (aRv.Failed()) {
return;
}
}
else {
mProxy = new Proxy(this);
}
mProxy->mOuterEventStreamId++;
nsRefPtr<OpenRunnable> runnable =
new OpenRunnable(mWorkerPrivate, mProxy, aMethod, aUrl, aUser, aPassword,
mMultipart, mBackgroundRequest, mWithCredentials,
mTimeout);
if (!runnable->Dispatch(GetJSContext())) {
ReleaseProxy();
aRv.Throw(NS_ERROR_FAILURE);
return;
}
mProxy->mIsSyncXHR = !aAsync;
}
void
XMLHttpRequest::SetRequestHeader(const nsAString& aHeader,
const nsAString& aValue, ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
if (!mProxy) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
nsRefPtr<SetRequestHeaderRunnable> runnable =
new SetRequestHeaderRunnable(mWorkerPrivate, mProxy,
NS_ConvertUTF16toUTF8(aHeader),
NS_ConvertUTF16toUTF8(aValue));
if (!runnable->Dispatch(GetJSContext())) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
}
void
XMLHttpRequest::SetTimeout(uint32_t aTimeout, ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
mTimeout = aTimeout;
if (!mProxy) {
// Open may not have been called yet, in which case we'll handle the
// timeout in OpenRunnable.
return;
}
nsRefPtr<SetTimeoutRunnable> runnable =
new SetTimeoutRunnable(mWorkerPrivate, mProxy, aTimeout);
if (!runnable->Dispatch(GetJSContext())) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
}
void
XMLHttpRequest::SetWithCredentials(bool aWithCredentials, ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
mWithCredentials = aWithCredentials;
if (!mProxy) {
// Open may not have been called yet, in which case we'll handle the
// credentials in OpenRunnable.
return;
}
nsRefPtr<SetWithCredentialsRunnable> runnable =
new SetWithCredentialsRunnable(mWorkerPrivate, mProxy, aWithCredentials);
if (!runnable->Dispatch(GetJSContext())) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
}
void
XMLHttpRequest::SetMultipart(bool aMultipart, ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
mMultipart = aMultipart;
if (!mProxy) {
// Open may not have been called yet, in which case we'll handle the
// multipart in OpenRunnable.
return;
}
nsRefPtr<SetMultipartRunnable> runnable =
new SetMultipartRunnable(mWorkerPrivate, mProxy, aMultipart);
if (!runnable->Dispatch(GetJSContext())) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
}
void
XMLHttpRequest::SetMozBackgroundRequest(bool aBackgroundRequest,
ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
mBackgroundRequest = aBackgroundRequest;
if (!mProxy) {
// Open may not have been called yet, in which case we'll handle the
// background request in OpenRunnable.
return;
}
nsRefPtr<SetBackgroundRequestRunnable> runnable =
new SetBackgroundRequestRunnable(mWorkerPrivate, mProxy,
aBackgroundRequest);
if (!runnable->Dispatch(GetJSContext())) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
}
XMLHttpRequestUpload*
XMLHttpRequest::GetUpload(ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return NULL;
}
if (!mUpload) {
XMLHttpRequestUpload* upload =
XMLHttpRequestUpload::Create(GetJSContext(), this);
if (!upload) {
aRv.Throw(NS_ERROR_FAILURE);
return NULL;
}
mUpload = upload;
}
return mUpload;
}
void
XMLHttpRequest::Send(ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
if (!mProxy) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// Nothing to clone.
JSAutoStructuredCloneBuffer buffer;
nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
SendInternal(NullString(), buffer, clonedObjects, aRv);
}
void
XMLHttpRequest::Send(const nsAString& aBody, ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
if (!mProxy) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// Nothing to clone.
JSAutoStructuredCloneBuffer buffer;
nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
SendInternal(aBody, buffer, clonedObjects, aRv);
}
void
XMLHttpRequest::Send(JSObject* aBody, ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(aBody);
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
if (!mProxy) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
JSContext* cx = GetJSContext();
jsval valToClone;
if (JS_IsArrayBufferObject(aBody, cx) || file::GetDOMBlobFromJSObject(aBody)) {
valToClone = OBJECT_TO_JSVAL(aBody);
}
else {
JSString* bodyStr = JS_ValueToString(cx, OBJECT_TO_JSVAL(aBody));
if (!bodyStr) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
valToClone = STRING_TO_JSVAL(bodyStr);
}
JSStructuredCloneCallbacks* callbacks =
mWorkerPrivate->IsChromeWorker() ?
ChromeWorkerStructuredCloneCallbacks(false) :
WorkerStructuredCloneCallbacks(false);
nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
JSAutoStructuredCloneBuffer buffer;
if (!buffer.write(cx, valToClone, callbacks, &clonedObjects)) {
aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
return;
}
SendInternal(EmptyString(), buffer, clonedObjects, aRv);
}
void
XMLHttpRequest::SendAsBinary(const nsAString& aBody, ErrorResult& aRv)
{
NS_NOTYETIMPLEMENTED("Implement me!");
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return;
}
void
XMLHttpRequest::Abort(ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
}
if (!mProxy) {
return;
}
MaybeDispatchPrematureAbortEvents(aRv);
if (aRv.Failed()) {
return;
}
mProxy->mOuterEventStreamId++;
nsRefPtr<AbortRunnable> runnable = new AbortRunnable(mWorkerPrivate, mProxy);
if (!runnable->Dispatch(GetJSContext())) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
}
void
XMLHttpRequest::GetResponseHeader(const nsAString& aHeader,
nsAString& aResponseHeader, ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
if (!mProxy) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
nsCString value;
nsRefPtr<GetResponseHeaderRunnable> runnable =
new GetResponseHeaderRunnable(mWorkerPrivate, mProxy,
NS_ConvertUTF16toUTF8(aHeader), value);
if (!runnable->Dispatch(GetJSContext())) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
aResponseHeader = NS_ConvertUTF8toUTF16(value);
}
void
XMLHttpRequest::GetAllResponseHeaders(nsAString& aResponseHeaders,
ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
if (!mProxy) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
nsString responseHeaders;
nsRefPtr<GetAllResponseHeadersRunnable> runnable =
new GetAllResponseHeadersRunnable(mWorkerPrivate, mProxy, responseHeaders);
if (!runnable->Dispatch(GetJSContext())) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
aResponseHeaders = responseHeaders;
}
void
XMLHttpRequest::OverrideMimeType(const nsAString& aMimeType, ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
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.
if (!mProxy || SendInProgress()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
nsRefPtr<OverrideMimeTypeRunnable> runnable =
new OverrideMimeTypeRunnable(mWorkerPrivate, mProxy, aMimeType);
if (!runnable->Dispatch(GetJSContext())) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
}
void
XMLHttpRequest::SetResponseType(XMLHttpRequestResponseType aResponseType,
ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.Throw(UNCATCHABLE_EXCEPTION);
return;
}
if (!mProxy || SendInProgress()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// "document" is fine for the main thread but not for a worker. Short-circuit
// that here.
if (aResponseType == XMLHttpRequestResponseTypeValues::Document) {
return;
}
nsString responseType;
ConvertResponseTypeToString(aResponseType, responseType);
nsRefPtr<SetResponseTypeRunnable> runnable =
new SetResponseTypeRunnable(mWorkerPrivate, mProxy, responseType);
if (!runnable->Dispatch(GetJSContext())) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
nsString acceptedResponseTypeString;
runnable->GetResponseType(acceptedResponseTypeString);
mResponseType = ConvertStringToResponseType(acceptedResponseTypeString);
}
jsval
XMLHttpRequest::GetResponse(JSContext* /* unused */, ErrorResult& aRv)
{
if (NS_SUCCEEDED(mStateData.mResponseTextResult) &&
JSVAL_IS_VOID(mStateData.mResponse)) {
MOZ_ASSERT(mStateData.mResponseText.Length());
MOZ_ASSERT(NS_SUCCEEDED(mStateData.mResponseResult));
JSString* str =
JS_NewUCStringCopyN(GetJSContext(), mStateData.mResponseText.get(),
mStateData.mResponseText.Length());
if (!str) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return JSVAL_VOID;
}
mStateData.mResponse = STRING_TO_JSVAL(str);
}
aRv = mStateData.mResponseResult;
return mStateData.mResponse;
}
void
XMLHttpRequest::GetResponseText(nsAString& aResponseText, ErrorResult& aRv)
{
aRv = mStateData.mResponseTextResult;
aResponseText = mStateData.mResponseText;
}