mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
56133baee1
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.
2195 lines
55 KiB
C++
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;
|
|
}
|