/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Web Workers. * * The Initial Developer of the Original Code is * The Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Ben Turner (Original Author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "XMLHttpRequestPrivate.h" #include "nsIDOMEvent.h" #include "nsIDOMEventListener.h" #include "nsIDOMProgressEvent.h" #include "nsIRunnable.h" #include "nsIXMLHttpRequest.h" #include "nsIXPConnect.h" #include "jstypedarray.h" #include "nsAutoPtr.h" #include "nsCOMPtr.h" #include "nsContentUtils.h" #include "nsJSUtils.h" #include "nsThreadUtils.h" #include "nsXMLHttpRequest.h" #include "Events.h" #include "EventTarget.h" #include "Exceptions.h" #include "RuntimeService.h" #include "XMLHttpRequest.h" /** * 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 everytime 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 namespace xhr { class Proxy : public nsIDOMEventListener { public: // Read on multiple threads. WorkerPrivate* mWorkerPrivate; XMLHttpRequestPrivate* mXMLHttpRequestPrivate; // Only touched on the main thread. nsRefPtr mXHR; nsCOMPtr 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. nsCString mPreviousStatusText; PRUint32 mSyncQueueKey; PRUint32 mSyncEventResponseSyncQueueKey; bool mUploadEventListenersAttached; bool mMainThreadSeenLoadStart; bool mInOpen; public: NS_DECL_ISUPPORTS NS_DECL_NSIDOMEVENTLISTENER Proxy(XMLHttpRequestPrivate* 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(); mPreviousStatusText.Truncate(); 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; } }; } // namespace xhr END_WORKERS_NAMESPACE USING_WORKERS_NAMESPACE using mozilla::dom::workers::xhr::XMLHttpRequestPrivate; using mozilla::dom::workers::xhr::Proxy; using mozilla::dom::workers::exceptions::ThrowDOMExceptionForCode; namespace { inline int GetDOMExceptionCodeFromResult(nsresult aResult) { if (NS_SUCCEEDED(aResult)) { return 0; } if (NS_ERROR_GET_MODULE(aResult) == NS_ERROR_MODULE_DOM) { return NS_ERROR_GET_CODE(aResult); } NS_WARNING("Update main thread implementation for a DOM error code here!"); return INVALID_STATE_ERR; } 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 mProxy; public: MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate, ClearingBehavior aClearingBehavior, Proxy* aProxy) : MainThreadSyncRunnable(aWorkerPrivate, aClearingBehavior, aProxy->GetSyncQueueKey(), aProxy->EventsBypassSyncQueue()), mProxy(aProxy) { } }; class XHRUnpinRunnable : public WorkerControlRunnable { XMLHttpRequestPrivate* mXMLHttpRequestPrivate; public: XHRUnpinRunnable(WorkerPrivate* aWorkerPrivate, XMLHttpRequestPrivate* 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(aCx); return true; } }; class AsyncTeardownRunnable : public nsRunnable { nsRefPtr 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 : public nsIRunnable, public nsIDOMEventListener { WorkerPrivate* mWorkerPrivate; nsRefPtr mProxy; nsRefPtr mXHR; XMLHttpRequestPrivate* mXMLHttpRequestPrivate; nsString mEventType; bool mReceivedLoadStart; PRUint32 mChannelId; class ProxyCompleteRunnable : public MainThreadProxyRunnable { XMLHttpRequestPrivate* mXMLHttpRequestPrivate; PRUint32 mChannelId; public: ProxyCompleteRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, XMLHttpRequestPrivate* 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(aCx); return true; } }; public: NS_DECL_ISUPPORTS LoadStartDetectionRunnable(Proxy* aProxy, XMLHttpRequestPrivate* 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 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 > mClonedObjects; jsval mResponse; nsCString mStatusText; PRUint64 mLoaded; PRUint64 mTotal; PRUint32 mEventStreamId; PRUint32 mStatus; PRUint16 mReadyState; bool mUploadEvent; bool mProgressEvent; bool mLengthComputable; bool mResponseTextException; bool mStatusException; bool mStatusTextException; bool mReadyStateException; bool mResponseException; 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), mResponseTextException(false), mStatusException(false), mStatusTextException(false), mReadyStateException(false), mResponseException(false) { } 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), mResponseTextException(false), mStatusException(false), mStatusTextException(false), mReadyStateException(false), mResponseException(false) { } bool PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { nsRefPtr& xhr = mProxy->mXHR; NS_ASSERTION(xhr, "Must have an XHR here!"); if (NS_FAILED(xhr->GetResponseType(mResponseType))) { NS_ERROR("This should never fail!"); } jsval response; if (NS_SUCCEEDED(xhr->GetResponse(aCx, &response))) { if (JSVAL_IS_UNIVERSAL(response)) { mResponse = response; } else { // Anything subject to GC must be cloned. JSStructuredCloneCallbacks* callbacks = aWorkerPrivate->IsChromeWorker() ? ChromeWorkerStructuredCloneCallbacks(true) : WorkerStructuredCloneCallbacks(true); nsTArray > clonedObjects; if (mResponseBuffer.write(aCx, response, callbacks, &clonedObjects)) { mClonedObjects.SwapElements(clonedObjects); } else { NS_ASSERTION(JS_IsExceptionPending(aCx), "This should really never fail unless OOM!"); mResponseException = true; } } } else { mResponseException = true; } nsString responseText; mResponseTextException = NS_FAILED(xhr->GetResponseText(responseText)); mStatusException = NS_FAILED(xhr->GetStatus(&mStatus)); if (NS_SUCCEEDED(xhr->GetStatusText(mStatusText))) { if (mStatusText == mProxy->mPreviousStatusText) { mStatusText.SetIsVoid(true); } else { mProxy->mPreviousStatusText = mStatusText; } mStatusTextException = false; } else { mStatusTextException = true; } mReadyStateException = NS_FAILED(xhr->GetReadyState(&mReadyState)); 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; } } JSObject* target = mUploadEvent ? mProxy->mXMLHttpRequestPrivate->GetUploadJSObject() : mProxy->mXMLHttpRequestPrivate->GetJSObject(); if (!target) { NS_ASSERTION(mUploadEvent, "How else is this possible?!"); return true; } xhr::StateData state; state.mResponseException = mResponseException; if (!mResponseException) { if (mResponseBuffer.data()) { NS_ASSERTION(JSVAL_IS_VOID(mResponse), "Huh?!"); JSStructuredCloneCallbacks* callbacks = aWorkerPrivate->IsChromeWorker() ? ChromeWorkerStructuredCloneCallbacks(false) : WorkerStructuredCloneCallbacks(false); nsTArray > clonedObjects; clonedObjects.SwapElements(mClonedObjects); jsval response; if (!mResponseBuffer.read(aCx, &response, callbacks, &clonedObjects)) { return false; } mResponseBuffer.clear(); state.mResponse = response; } else { state.mResponse = mResponse; } } // This logic is all based on the assumption that mResponseTextException // should be set if the responseType isn't "text". Otherwise we're going to // hand out the wrong result if someone gets the responseText property. state.mResponseTextException = mResponseTextException; if (!mResponseTextException) { NS_ASSERTION(JSVAL_IS_STRING(state.mResponse) || JSVAL_IS_NULL(state.mResponse), "Bad response!"); state.mResponseText = state.mResponse; } else { state.mResponseText = JSVAL_VOID; } state.mStatusException = mStatusException; state.mStatus = mStatusException ? JSVAL_VOID : INT_TO_JSVAL(mStatus); state.mStatusTextException = mStatusTextException; if (mStatusTextException || mStatusText.IsVoid()) { state.mStatusText = JSVAL_VOID; } else if (mStatusText.IsEmpty()) { state.mStatusText = JS_GetEmptyStringValue(aCx); } else { JSString* statusText = JS_NewStringCopyN(aCx, mStatusText.get(), mStatusText.Length()); if (!statusText) { return false; } mStatusText.Truncate(); state.mStatusText = STRING_TO_JSVAL(statusText); } state.mReadyStateException = mReadyStateException; state.mReadyState = mReadyStateException ? JSVAL_VOID : INT_TO_JSVAL(mReadyState); if (!xhr::UpdateXHRState(aCx, target, mUploadEvent, state)) { return false; } 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; } 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::StateData newState = { JSVAL_NULL, JSVAL_VOID, JSVAL_VOID, JSVAL_VOID, JSVAL_NULL, false, false, false, false, false }; if (!xhr::UpdateXHRState(aCx, target, mUploadEvent, newState)) { return false; } } return true; } }; class WorkerThreadProxySyncRunnable : public nsRunnable { protected: WorkerPrivate* mWorkerPrivate; nsRefPtr mProxy; PRUint32 mSyncQueueKey; private: class ResponseRunnable : public MainThreadProxyRunnable { PRUint32 mSyncQueueKey; int mErrorCode; public: ResponseRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, PRUint32 aSyncQueueKey, int 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 (mErrorCode) { ThrowDOMExceptionForCode(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!"); MOZ_COUNT_CTOR(mozilla::dom::workers::xhr::WorkerThreadProxySyncRunnable); } ~WorkerThreadProxySyncRunnable() { MOZ_COUNT_DTOR(mozilla::dom::workers::xhr::WorkerThreadProxySyncRunnable); } bool Dispatch(JSContext* aCx) { mWorkerPrivate->AssertIsOnWorkerThread(); mSyncQueueKey = mWorkerPrivate->CreateNewSyncLoop(); if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { JS_ReportError(aCx, "Failed to initialize XHR!"); return false; } if (!mWorkerPrivate->RunSyncLoop(aCx, mSyncQueueKey)) { return false; } return true; } virtual int MainThreadRun() = 0; NS_IMETHOD Run() { AssertIsOnMainThread(); PRUint32 oldSyncQueueKey = mProxy->mSyncEventResponseSyncQueueKey; mProxy->mSyncEventResponseSyncQueueKey = mSyncQueueKey; int rv = MainThreadRun(); nsRefPtr 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 int 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) { } int MainThreadRun() { return GetDOMExceptionCodeFromResult(mProxy->mXHR->SetMultipart(mValue)); } }; class SetBackgroundRequestRunnable : public WorkerThreadProxySyncRunnable { bool mValue; public: SetBackgroundRequestRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, bool aValue) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mValue(aValue) { } int MainThreadRun() { nsresult rv = mProxy->mXHR->SetMozBackgroundRequest(mValue); return GetDOMExceptionCodeFromResult(rv); } }; class SetWithCredentialsRunnable : public WorkerThreadProxySyncRunnable { bool mValue; public: SetWithCredentialsRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, bool aValue) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mValue(aValue) { } int MainThreadRun() { nsresult rv = mProxy->mXHR->SetWithCredentials(mValue); return GetDOMExceptionCodeFromResult(rv); } }; class SetResponseTypeRunnable : public WorkerThreadProxySyncRunnable { nsString mResponseType; public: SetResponseTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, const nsAString& aResponseType) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mResponseType(aResponseType) { } int MainThreadRun() { nsresult rv = mProxy->mXHR->SetResponseType(mResponseType); mResponseType.Truncate(); if (NS_SUCCEEDED(rv)) { rv = mProxy->mXHR->GetResponseType(mResponseType); } return GetDOMExceptionCodeFromResult(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) { } int MainThreadRun() { return GetDOMExceptionCodeFromResult(mProxy->mXHR->SetTimeout(mTimeout)); } }; class AbortRunnable : public WorkerThreadProxySyncRunnable { public: AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy) { } int MainThreadRun() { mProxy->mInnerEventStreamId++; WorkerPrivate* oldWorker = mProxy->mWorkerPrivate; mProxy->mWorkerPrivate = mWorkerPrivate; nsresult rv = mProxy->mXHR->Abort(); mProxy->mWorkerPrivate = oldWorker; mProxy->Reset(); return GetDOMExceptionCodeFromResult(rv); } }; class GetAllResponseHeadersRunnable : public WorkerThreadProxySyncRunnable { nsString& mResponseHeaders; public: GetAllResponseHeadersRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, nsString& aResponseHeaders) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mResponseHeaders(aResponseHeaders) { } int MainThreadRun() { nsresult rv = mProxy->mXHR->GetAllResponseHeaders(mResponseHeaders); return GetDOMExceptionCodeFromResult(rv); } }; 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) { } int MainThreadRun() { nsresult rv = mProxy->mXHR->GetResponseHeader(mHeader, mValue); return GetDOMExceptionCodeFromResult(rv); } }; class OpenRunnable : public WorkerThreadProxySyncRunnable { nsCString mMethod; nsCString mURL; nsString mUser; nsString mPassword; bool mMultipart; bool mBackgroundRequest; bool mWithCredentials; PRUint32 mTimeout; public: OpenRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, const nsCString& aMethod, const nsCString& aURL, const nsString& aUser, const nsString& aPassword, bool aMultipart, bool aBackgroundRequest, bool aWithCredentials, PRUint32 aTimeout) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMethod(aMethod), mURL(aURL), mUser(aUser), mPassword(aPassword), mMultipart(aMultipart), mBackgroundRequest(aBackgroundRequest), mWithCredentials(aWithCredentials), mTimeout(aTimeout) { } int MainThreadRun() { WorkerPrivate* oldWorker = mProxy->mWorkerPrivate; mProxy->mWorkerPrivate = mWorkerPrivate; int retval = MainThreadRunInternal(); mProxy->mWorkerPrivate = oldWorker; return retval; } int MainThreadRunInternal() { if (!mProxy->Init()) { return INVALID_STATE_ERR; } nsresult rv; if (mMultipart) { rv = mProxy->mXHR->SetMultipart(mMultipart); if (NS_FAILED(rv)) { return GetDOMExceptionCodeFromResult(rv); } } if (mBackgroundRequest) { rv = mProxy->mXHR->SetMozBackgroundRequest(mBackgroundRequest); if (NS_FAILED(rv)) { return GetDOMExceptionCodeFromResult(rv); } } if (mWithCredentials) { rv = mProxy->mXHR->SetWithCredentials(mWithCredentials); if (NS_FAILED(rv)) { return GetDOMExceptionCodeFromResult(rv); } } if (mTimeout) { rv = mProxy->mXHR->SetTimeout(mTimeout); if (NS_FAILED(rv)) { return GetDOMExceptionCodeFromResult(rv); } } mProxy->mPreviousStatusText.Truncate(); NS_ASSERTION(!mProxy->mInOpen, "Reentrancy is bad!"); mProxy->mInOpen = true; rv = mProxy->mXHR->Open(mMethod, mURL, true, mUser, mPassword, 1); NS_ASSERTION(mProxy->mInOpen, "Reentrancy is bad!"); mProxy->mInOpen = false; if (NS_SUCCEEDED(rv)) { rv = mProxy->mXHR->SetResponseType(NS_LITERAL_STRING("text")); } return GetDOMExceptionCodeFromResult(rv); } }; class SendRunnable : public WorkerThreadProxySyncRunnable { JSAutoStructuredCloneBuffer mBody; PRUint32 mSyncQueueKey; bool mHasUploadListeners; public: SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, JSAutoStructuredCloneBuffer& aBody, PRUint32 aSyncQueueKey, bool aHasUploadListeners) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mSyncQueueKey(aSyncQueueKey), mHasUploadListeners(aHasUploadListeners) { mBody.swap(aBody); } int MainThreadRun() { NS_ASSERTION(!mProxy->mWorkerPrivate, "Should be null!"); mProxy->mWorkerPrivate = mWorkerPrivate; NS_ASSERTION(mProxy->mSyncQueueKey == PR_UINT32_MAX, "Should be unset!"); mProxy->mSyncQueueKey = mSyncQueueKey; nsCOMPtr variant; if (mBody.data()) { nsIXPConnect* xpc = nsContentUtils::XPConnect(); NS_ASSERTION(xpc, "This should never be null!"); RuntimeService::AutoSafeJSContext cx; int error = 0; jsval body; if (mBody.read(cx, &body)) { if (NS_FAILED(xpc->JSValToVariant(cx, &body, getter_AddRefs(variant)))) { error = INVALID_STATE_ERR; } } else { error = DATA_CLONE_ERR; } mBody.clear(); if (error) { return error; } } 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 GetDOMExceptionCodeFromResult(rv); } }; class SendAsBinaryRunnable : public WorkerThreadProxySyncRunnable { nsString mBody; PRUint32 mSyncQueueKey; bool mHasUploadListeners; public: SendAsBinaryRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, const nsString& aBody, PRUint32 aSyncQueueKey, bool aHasUploadListeners) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mBody(aBody), mSyncQueueKey(aSyncQueueKey), mHasUploadListeners(aHasUploadListeners) { } int MainThreadRun() { 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!"); } } nsresult rv = mProxy->mXHR->SendAsBinary(mBody); if (NS_SUCCEEDED(rv) && !mHasUploadListeners) { NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!"); if (!mProxy->AddRemoveEventListeners(true, true)) { NS_ERROR("This should never fail!"); } } return GetDOMExceptionCodeFromResult(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) { } int MainThreadRun() { nsresult rv = mProxy->mXHR->SetRequestHeader(mHeader, mValue); return GetDOMExceptionCodeFromResult(rv); } }; class OverrideMimeTypeRunnable : public WorkerThreadProxySyncRunnable { nsCString mMimeType; public: OverrideMimeTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, const nsCString& aMimeType) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMimeType(aMimeType) { } int MainThreadRun() { nsresult rv = mProxy->mXHR->OverrideMimeType(mMimeType); return GetDOMExceptionCodeFromResult(rv); } }; class AutoUnpinXHR { public: AutoUnpinXHR(XMLHttpRequestPrivate* aXMLHttpRequestPrivate, JSContext* aCx) : mXMLHttpRequestPrivate(aXMLHttpRequestPrivate), mCx(aCx) { } ~AutoUnpinXHR() { if (mXMLHttpRequestPrivate) { mXMLHttpRequestPrivate->Unpin(mCx); } } void Clear() { mXMLHttpRequestPrivate = nsnull; } private: XMLHttpRequestPrivate* mXMLHttpRequestPrivate; JSContext* mCx; }; } // 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 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 target = aUpload ? do_QueryInterface(mXHRUpload) : do_QueryInterface(static_cast(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 target; if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target)))) { NS_WARNING("Failed to get target!"); return NS_ERROR_FAILURE; } nsCOMPtr uploadTarget = do_QueryInterface(target); nsCOMPtr progressEvent = do_QueryInterface(aEvent); nsRefPtr 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 runnable = new LoadStartDetectionRunnable(this, mXMLHttpRequestPrivate); if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { NS_WARNING("Failed to dispatch LoadStartDetectionRunnable!"); } } } return NS_OK; } XMLHttpRequestPrivate::XMLHttpRequestPrivate(JSObject* aObj, WorkerPrivate* aWorkerPrivate) : mJSObject(aObj), mUploadJSObject(nsnull), mWorkerPrivate(aWorkerPrivate), mJSObjectRooted(false), mMultipart(false), mBackgroundRequest(false), mWithCredentials(false), mCanceled(false), mTimeout(0) { mWorkerPrivate->AssertIsOnWorkerThread(); MOZ_COUNT_CTOR(mozilla::dom::workers::xhr::XMLHttpRequestPrivate); } XMLHttpRequestPrivate::~XMLHttpRequestPrivate() { mWorkerPrivate->AssertIsOnWorkerThread(); NS_ASSERTION(!mJSObjectRooted, "Huh?!"); MOZ_COUNT_DTOR(mozilla::dom::workers::xhr::XMLHttpRequestPrivate); } void XMLHttpRequestPrivate::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 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 runnable = new SyncTeardownRunnable(mWorkerPrivate, mProxy); mProxy = nsnull; if (!runnable->Dispatch(nsnull)) { NS_ERROR("Failed to dispatch teardown runnable!"); } } } } bool XMLHttpRequestPrivate::MaybePin(JSContext* aCx) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mJSObjectRooted) { return true; } if (!JS_AddNamedObjectRoot(aCx, &mJSObject, "XMLHttpRequestPrivate mJSObject")) { return false; } if (!mWorkerPrivate->AddFeature(aCx, this)) { if (!JS_RemoveObjectRoot(aCx, &mJSObject)) { NS_ERROR("JS_RemoveObjectRoot failed!"); } return false; } mJSObjectRooted = true; return true; } void XMLHttpRequestPrivate::Unpin(JSContext* aCx) { mWorkerPrivate->AssertIsOnWorkerThread(); NS_ASSERTION(mJSObjectRooted, "Mismatched calls to Unpin!"); if (!JS_RemoveObjectRoot(aCx, &mJSObject)) { NS_ERROR("JS_RemoveObjectRoot failed!"); } mWorkerPrivate->RemoveFeature(aCx, this); mJSObjectRooted = false; } bool XMLHttpRequestPrivate::Notify(JSContext* aCx, Status aStatus) { mWorkerPrivate->AssertIsOnWorkerThread(); if (aStatus >= Canceling && !mCanceled) { mCanceled = true; ReleaseProxy(WorkerIsGoingAway); } return true; } bool XMLHttpRequestPrivate::SetMultipart(JSContext* aCx, jsval aOldVal, jsval *aVp) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return false; } JSBool multipart; if (!JS_ValueToBoolean(aCx, *aVp, &multipart)) { return false; } *aVp = multipart ? JSVAL_TRUE : JSVAL_FALSE; if (!mProxy) { mMultipart = !!multipart; return true; } nsRefPtr runnable = new SetMultipartRunnable(mWorkerPrivate, mProxy, multipart); if (!runnable->Dispatch(aCx)) { return false; } return true; } bool XMLHttpRequestPrivate::SetMozBackgroundRequest(JSContext* aCx, jsval aOldVal, jsval *aVp) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return false; } JSBool backgroundRequest; if (!JS_ValueToBoolean(aCx, *aVp, &backgroundRequest)) { return false; } *aVp = backgroundRequest ? JSVAL_TRUE : JSVAL_FALSE; if (!mProxy) { mBackgroundRequest = !!backgroundRequest; return true; } nsRefPtr runnable = new SetBackgroundRequestRunnable(mWorkerPrivate, mProxy, backgroundRequest); if (!runnable->Dispatch(aCx)) { return false; } return true; } bool XMLHttpRequestPrivate::SetWithCredentials(JSContext* aCx, jsval aOldVal, jsval *aVp) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return false; } JSBool withCredentials; if (!JS_ValueToBoolean(aCx, *aVp, &withCredentials)) { return false; } *aVp = withCredentials ? JSVAL_TRUE : JSVAL_FALSE; if (!mProxy) { mWithCredentials = !!withCredentials; return true; } nsRefPtr runnable = new SetWithCredentialsRunnable(mWorkerPrivate, mProxy, withCredentials); if (!runnable->Dispatch(aCx)) { return false; } return true; } bool XMLHttpRequestPrivate::SetResponseType(JSContext* aCx, jsval aOldVal, jsval *aVp) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return false; } if (!mProxy || SendInProgress()) { ThrowDOMExceptionForCode(aCx, INVALID_STATE_ERR); return false; } JSString* jsstr = JS_ValueToString(aCx, *aVp); if (!jsstr) { return false; } nsDependentJSString responseType; if (!responseType.init(aCx, jsstr)) { return false; } // "document" is fine for the main thread but not for a worker. Short-circuit // that here. if (responseType.EqualsLiteral("document")) { *aVp = aOldVal; return true; } nsRefPtr runnable = new SetResponseTypeRunnable(mWorkerPrivate, mProxy, responseType); if (!runnable->Dispatch(aCx)) { return false; } nsString acceptedResponseType; runnable->GetResponseType(acceptedResponseType); if (acceptedResponseType == responseType) { // Leave *aVp unchanged. } else if (acceptedResponseType.IsEmpty()) { // Empty string. *aVp = JS_GetEmptyStringValue(aCx); } else { // Some other string. jsstr = JS_NewUCStringCopyN(aCx, acceptedResponseType.get(), acceptedResponseType.Length()); if (!jsstr) { return false; } *aVp = STRING_TO_JSVAL(jsstr); } return true; } bool XMLHttpRequestPrivate::SetTimeout(JSContext* aCx, jsval aOldVal, jsval *aVp) { mWorkerPrivate->AssertIsOnWorkerThread(); uint32_t timeout; if (!JS_ValueToECMAUint32(aCx, *aVp, &timeout)) { return false; } mTimeout = timeout; if (!mProxy) { // Open may not have been called yet, in which case we'll handle the // timeout in OpenRunnable. return true; } nsRefPtr runnable = new SetTimeoutRunnable(mWorkerPrivate, mProxy, timeout); if (!runnable->Dispatch(aCx)) { return false; } return true; } bool XMLHttpRequestPrivate::Abort(JSContext* aCx) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return false; } if (mProxy) { if (!MaybeDispatchPrematureAbortEvents(aCx)) { return false; } } else { return true; } mProxy->mOuterEventStreamId++; nsRefPtr runnable = new AbortRunnable(mWorkerPrivate, mProxy); return runnable->Dispatch(aCx); } JSString* XMLHttpRequestPrivate::GetAllResponseHeaders(JSContext* aCx) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return nsnull; } if (!mProxy) { ThrowDOMExceptionForCode(aCx, INVALID_STATE_ERR); return nsnull; } nsString responseHeaders; nsRefPtr runnable = new GetAllResponseHeadersRunnable(mWorkerPrivate, mProxy, responseHeaders); if (!runnable->Dispatch(aCx)) { return nsnull; } return JS_NewUCStringCopyN(aCx, responseHeaders.get(), responseHeaders.Length()); } JSString* XMLHttpRequestPrivate::GetResponseHeader(JSContext* aCx, JSString* aHeader) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return nsnull; } if (!mProxy) { ThrowDOMExceptionForCode(aCx, INVALID_STATE_ERR); return nsnull; } nsDependentJSString header; if (!header.init(aCx, aHeader)) { return nsnull; } nsCString value; nsRefPtr runnable = new GetResponseHeaderRunnable(mWorkerPrivate, mProxy, NS_ConvertUTF16toUTF8(header), value); if (!runnable->Dispatch(aCx)) { return nsnull; } return JS_NewStringCopyN(aCx, value.get(), value.Length()); } bool XMLHttpRequestPrivate::Open(JSContext* aCx, JSString* aMethod, JSString* aURL, bool aAsync, JSString* aUser, JSString* aPassword) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return false; } nsDependentJSString method, url, user, password; if (!method.init(aCx, aMethod) || !url.init(aCx, aURL) || !user.init(aCx, aUser) || !password.init(aCx, aPassword)) { return false; } if (mProxy) { if (!MaybeDispatchPrematureAbortEvents(aCx)) { return false; } } else { mProxy = new Proxy(this); } mProxy->mOuterEventStreamId++; nsRefPtr runnable = new OpenRunnable(mWorkerPrivate, mProxy, NS_ConvertUTF16toUTF8(method), NS_ConvertUTF16toUTF8(url), user, password, mMultipart, mBackgroundRequest, mWithCredentials, mTimeout); // These were only useful before we had a proxy. From here on out changing // those values makes no difference. mMultipart = mBackgroundRequest = mWithCredentials = false; if (!runnable->Dispatch(aCx)) { ReleaseProxy(); return false; } mProxy->mIsSyncXHR = !aAsync; return true; } bool XMLHttpRequestPrivate::Send(JSContext* aCx, bool aHasBody, jsval aBody) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return false; } JSAutoStructuredCloneBuffer buffer; if (aHasBody) { if (JSVAL_IS_PRIMITIVE(aBody) || !js_IsArrayBuffer(JSVAL_TO_OBJECT(aBody))) { JSString* bodyStr = JS_ValueToString(aCx, aBody); if (!bodyStr) { return false; } aBody = STRING_TO_JSVAL(bodyStr); } if (!buffer.write(aCx, aBody)) { return false; } } if (!mProxy) { ThrowDOMExceptionForCode(aCx, INVALID_STATE_ERR); return false; } bool hasUploadListeners = false; if (mUploadJSObject) { events::EventTarget* target = events::EventTarget::FromJSObject(mUploadJSObject); NS_ASSERTION(target, "This should never be null!"); hasUploadListeners = target->HasListeners(); } if (!MaybePin(aCx)) { return false; } AutoUnpinXHR autoUnpin(this, aCx); PRUint32 syncQueueKey = PR_UINT32_MAX; if (mProxy->mIsSyncXHR) { syncQueueKey = mWorkerPrivate->CreateNewSyncLoop(); } mProxy->mOuterChannelId++; nsRefPtr runnable = new SendRunnable(mWorkerPrivate, mProxy, buffer, syncQueueKey, hasUploadListeners); if (!runnable->Dispatch(aCx)) { return false; } autoUnpin.Clear(); // The event loop was spun above, make sure we aren't canceled already. if (mCanceled) { return false; } return mProxy->mIsSyncXHR ? mWorkerPrivate->RunSyncLoop(aCx, syncQueueKey) : true; } bool XMLHttpRequestPrivate::SendAsBinary(JSContext* aCx, JSString* aBody) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return false; } if (!mProxy) { ThrowDOMExceptionForCode(aCx, INVALID_STATE_ERR); return false; } nsDependentJSString body; if (!body.init(aCx, aBody)) { return false; } bool hasUploadListeners = false; if (mUploadJSObject) { events::EventTarget* target = events::EventTarget::FromJSObject(mUploadJSObject); NS_ASSERTION(target, "This should never be null!"); hasUploadListeners = target->HasListeners(); } if (!MaybePin(aCx)) { return false; } AutoUnpinXHR autoUnpin(this, aCx); PRUint32 syncQueueKey = PR_UINT32_MAX; if (mProxy->mIsSyncXHR) { syncQueueKey = mWorkerPrivate->CreateNewSyncLoop(); } nsRefPtr runnable = new SendAsBinaryRunnable(mWorkerPrivate, mProxy, body, syncQueueKey, hasUploadListeners); if (!runnable->Dispatch(aCx)) { return false; } autoUnpin.Clear(); // The event loop was spun above, make sure we aren't canceled already. if (mCanceled) { return false; } return mProxy->mIsSyncXHR ? mWorkerPrivate->RunSyncLoop(aCx, syncQueueKey) : true; } bool XMLHttpRequestPrivate::SetRequestHeader(JSContext* aCx, JSString* aHeader, JSString* aValue) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return false; } if (!mProxy) { ThrowDOMExceptionForCode(aCx, INVALID_STATE_ERR); return false; } nsDependentJSString header, value; if (!header.init(aCx, aHeader) || !value.init(aCx, aValue)) { return false; } nsRefPtr runnable = new SetRequestHeaderRunnable(mWorkerPrivate, mProxy, NS_ConvertUTF16toUTF8(header), NS_ConvertUTF16toUTF8(value)); return runnable->Dispatch(aCx); } bool XMLHttpRequestPrivate::OverrideMimeType(JSContext* aCx, JSString* aMimeType) { mWorkerPrivate->AssertIsOnWorkerThread(); if (mCanceled) { return false; } // 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()) { ThrowDOMExceptionForCode(aCx, INVALID_STATE_ERR); return false; } nsDependentJSString mimeType; if (!mimeType.init(aCx, aMimeType)) { return false; } nsRefPtr runnable = new OverrideMimeTypeRunnable(mWorkerPrivate, mProxy, NS_ConvertUTF16toUTF8(mimeType)); return runnable->Dispatch(aCx); } bool XMLHttpRequestPrivate::MaybeDispatchPrematureAbortEvents(JSContext* aCx) { mWorkerPrivate->AssertIsOnWorkerThread(); NS_ASSERTION(mProxy, "Must have a proxy here!"); xhr::StateData state = { JSVAL_VOID, JSVAL_VOID, JSVAL_VOID, INT_TO_JSVAL(4), JSVAL_VOID, false, false, false, false, false }; if (mProxy->mSeenUploadLoadStart) { JSObject* target = mProxy->mXMLHttpRequestPrivate->GetUploadJSObject(); NS_ASSERTION(target, "Must have a target!"); if (!xhr::UpdateXHRState(aCx, target, true, state) || !DispatchPrematureAbortEvent(aCx, target, STRING_abort, true) || !DispatchPrematureAbortEvent(aCx, target, STRING_loadend, true)) { return false; } mProxy->mSeenUploadLoadStart = false; } if (mProxy->mSeenLoadStart) { JSObject* target = mProxy->mXMLHttpRequestPrivate->GetJSObject(); NS_ASSERTION(target, "Must have a target!"); if (!xhr::UpdateXHRState(aCx, target, false, state) || !DispatchPrematureAbortEvent(aCx, target, STRING_readystatechange, false)) { return false; } if (!DispatchPrematureAbortEvent(aCx, target, STRING_abort, false) || !DispatchPrematureAbortEvent(aCx, target, STRING_loadend, false)) { return false; } mProxy->mSeenLoadStart = false; } return true; } bool XMLHttpRequestPrivate::DispatchPrematureAbortEvent(JSContext* aCx, JSObject* aTarget, PRUint64 aEventType, bool aUploadTarget) { mWorkerPrivate->AssertIsOnWorkerThread(); NS_ASSERTION(mProxy, "Must have a proxy here!"); NS_ASSERTION(aTarget, "Don't call me without a target!"); NS_ASSERTION(aEventType <= STRING_COUNT, "Bad string index!"); JSString* type = JS_NewStringCopyZ(aCx, sEventStrings[aEventType]); if (!type) { return false; } JSObject* event; if (aEventType == STRING_readystatechange) { event = events::CreateGenericEvent(aCx, type, false, false, false); } else { if (aUploadTarget) { event = events::CreateProgressEvent(aCx, type, mProxy->mLastUploadLengthComputable, mProxy->mLastUploadLoaded, mProxy->mLastUploadTotal); } else { event = events::CreateProgressEvent(aCx, type, mProxy->mLastLengthComputable, mProxy->mLastLoaded, mProxy->mLastTotal); } } if (!event) { return false; } bool dummy; return events::DispatchEventToTarget(aCx, aTarget, event, &dummy); }