michael@0: /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "XMLHttpRequest.h" michael@0: michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsIDOMEventListener.h" michael@0: #include "nsIDOMProgressEvent.h" michael@0: #include "nsIRunnable.h" michael@0: #include "nsIVariant.h" michael@0: #include "nsIXMLHttpRequest.h" michael@0: #include "nsIXPConnect.h" michael@0: michael@0: #include "jsfriendapi.h" michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/dom/Exceptions.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: #include "File.h" michael@0: #include "RuntimeService.h" michael@0: #include "WorkerPrivate.h" michael@0: #include "WorkerRunnable.h" michael@0: #include "XMLHttpRequestUpload.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: using namespace mozilla::dom; michael@0: USING_WORKERS_NAMESPACE michael@0: michael@0: // XXX Need to figure this out... michael@0: #define UNCATCHABLE_EXCEPTION NS_ERROR_OUT_OF_MEMORY michael@0: michael@0: /** michael@0: * XMLHttpRequest in workers michael@0: * michael@0: * XHR in workers is implemented by proxying calls/events/etc between the michael@0: * worker thread and an nsXMLHttpRequest on the main thread. The glue michael@0: * object here is the Proxy, which lives on both threads. All other objects michael@0: * live on either the main thread (the nsXMLHttpRequest) or the worker thread michael@0: * (the worker and XHR private objects). michael@0: * michael@0: * The main thread XHR is always operated in async mode, even for sync XHR michael@0: * in workers. Calls made on the worker thread are proxied to the main thread michael@0: * synchronously (meaning the worker thread is blocked until the call michael@0: * returns). Each proxied call spins up a sync queue, which captures any michael@0: * synchronously dispatched events and ensures that they run synchronously michael@0: * on the worker as well. Asynchronously dispatched events are posted to the michael@0: * worker thread to run asynchronously. Some of the XHR state is mirrored on michael@0: * the worker thread to avoid needing a cross-thread call on every property michael@0: * access. michael@0: * michael@0: * The XHR private is stored in the private slot of the XHR JSObject on the michael@0: * worker thread. It is destroyed when that JSObject is GCd. The private michael@0: * roots its JSObject while network activity is in progress. It also michael@0: * adds itself as a feature to the worker to give itself a chance to clean up michael@0: * if the worker goes away during an XHR call. It is important that the michael@0: * rooting and feature registration (collectively called pinning) happens at michael@0: * the proper times. If we pin for too long we can cause memory leaks or even michael@0: * shutdown hangs. If we don't pin for long enough we introduce a GC hazard. michael@0: * michael@0: * The XHR is pinned from the time Send is called to roughly the time loadend michael@0: * is received. There are some complications involved with Abort and XHR michael@0: * reuse. We maintain a counter on the main thread of how many times Send was michael@0: * called on this XHR, and we decrement the counter every time we receive a michael@0: * loadend event. When the counter reaches zero we dispatch a runnable to the michael@0: * worker thread to unpin the XHR. We only decrement the counter if the michael@0: * dispatch was successful, because the worker may no longer be accepting michael@0: * regular runnables. In the event that we reach Proxy::Teardown and there michael@0: * the outstanding Send count is still non-zero, we dispatch a control michael@0: * runnable which is guaranteed to run. michael@0: * michael@0: * NB: Some of this could probably be simplified now that we have the michael@0: * inner/outer channel ids. michael@0: */ michael@0: michael@0: BEGIN_WORKERS_NAMESPACE michael@0: michael@0: class Proxy MOZ_FINAL : public nsIDOMEventListener michael@0: { michael@0: public: michael@0: // Read on multiple threads. michael@0: WorkerPrivate* mWorkerPrivate; michael@0: XMLHttpRequest* mXMLHttpRequestPrivate; michael@0: michael@0: // XHR Params: michael@0: bool mMozAnon; michael@0: bool mMozSystem; michael@0: michael@0: // Only touched on the main thread. michael@0: nsRefPtr mXHR; michael@0: nsCOMPtr mXHRUpload; michael@0: nsCOMPtr mSyncLoopTarget; michael@0: nsCOMPtr mSyncEventResponseTarget; michael@0: uint32_t mInnerEventStreamId; michael@0: uint32_t mInnerChannelId; michael@0: uint32_t mOutstandingSendCount; michael@0: michael@0: // Only touched on the worker thread. michael@0: uint32_t mOuterEventStreamId; michael@0: uint32_t mOuterChannelId; michael@0: uint64_t mLastLoaded; michael@0: uint64_t mLastTotal; michael@0: uint64_t mLastUploadLoaded; michael@0: uint64_t mLastUploadTotal; michael@0: bool mIsSyncXHR; michael@0: bool mLastLengthComputable; michael@0: bool mLastUploadLengthComputable; michael@0: bool mSeenLoadStart; michael@0: bool mSeenUploadLoadStart; michael@0: michael@0: // Only touched on the main thread. michael@0: bool mUploadEventListenersAttached; michael@0: bool mMainThreadSeenLoadStart; michael@0: bool mInOpen; michael@0: michael@0: public: michael@0: Proxy(XMLHttpRequest* aXHRPrivate, bool aMozAnon, bool aMozSystem) michael@0: : mWorkerPrivate(nullptr), mXMLHttpRequestPrivate(aXHRPrivate), michael@0: mMozAnon(aMozAnon), mMozSystem(aMozSystem), michael@0: mInnerEventStreamId(0), mInnerChannelId(0), mOutstandingSendCount(0), michael@0: mOuterEventStreamId(0), mOuterChannelId(0), mLastLoaded(0), mLastTotal(0), michael@0: mLastUploadLoaded(0), mLastUploadTotal(0), mIsSyncXHR(false), michael@0: mLastLengthComputable(false), mLastUploadLengthComputable(false), michael@0: mSeenLoadStart(false), mSeenUploadLoadStart(false), michael@0: mUploadEventListenersAttached(false), mMainThreadSeenLoadStart(false), michael@0: mInOpen(false) michael@0: { } michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIDOMEVENTLISTENER michael@0: michael@0: bool michael@0: Init(); michael@0: michael@0: void michael@0: Teardown(); michael@0: michael@0: bool michael@0: AddRemoveEventListeners(bool aUpload, bool aAdd); michael@0: michael@0: void michael@0: Reset() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (mUploadEventListenersAttached) { michael@0: AddRemoveEventListeners(true, false); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: GetEventTarget() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsCOMPtr target = mSyncEventResponseTarget ? michael@0: mSyncEventResponseTarget : michael@0: mSyncLoopTarget; michael@0: return target.forget(); michael@0: } michael@0: michael@0: private: michael@0: ~Proxy() michael@0: { michael@0: MOZ_ASSERT(!mXHR); michael@0: MOZ_ASSERT(!mXHRUpload); michael@0: MOZ_ASSERT(!mOutstandingSendCount); michael@0: } michael@0: }; michael@0: michael@0: END_WORKERS_NAMESPACE michael@0: michael@0: namespace { michael@0: michael@0: inline void michael@0: ConvertResponseTypeToString(XMLHttpRequestResponseType aType, michael@0: nsString& aString) michael@0: { michael@0: using namespace michael@0: mozilla::dom::XMLHttpRequestResponseTypeValues; michael@0: michael@0: size_t index = static_cast(aType); michael@0: MOZ_ASSERT(index < ArrayLength(strings), "Codegen gave us a bad value!"); michael@0: michael@0: aString.AssignASCII(strings[index].value, strings[index].length); michael@0: } michael@0: michael@0: inline XMLHttpRequestResponseType michael@0: ConvertStringToResponseType(const nsAString& aString) michael@0: { michael@0: using namespace michael@0: mozilla::dom::XMLHttpRequestResponseTypeValues; michael@0: michael@0: for (size_t index = 0; index < ArrayLength(strings) - 1; index++) { michael@0: if (aString.EqualsASCII(strings[index].value, strings[index].length)) { michael@0: return static_cast(index); michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSUME_UNREACHABLE("Don't know anything about this response type!"); michael@0: } michael@0: michael@0: enum michael@0: { michael@0: STRING_abort = 0, michael@0: STRING_error, michael@0: STRING_load, michael@0: STRING_loadstart, michael@0: STRING_progress, michael@0: STRING_timeout, michael@0: STRING_readystatechange, michael@0: STRING_loadend, michael@0: michael@0: STRING_COUNT, michael@0: michael@0: STRING_LAST_XHR = STRING_loadend, michael@0: STRING_LAST_EVENTTARGET = STRING_timeout michael@0: }; michael@0: michael@0: static_assert(STRING_LAST_XHR >= STRING_LAST_EVENTTARGET, "Bad string setup!"); michael@0: static_assert(STRING_LAST_XHR == STRING_COUNT - 1, "Bad string setup!"); michael@0: michael@0: const char* const sEventStrings[] = { michael@0: // nsIXMLHttpRequestEventTarget event types, supported by both XHR and Upload. michael@0: "abort", michael@0: "error", michael@0: "load", michael@0: "loadstart", michael@0: "progress", michael@0: "timeout", michael@0: michael@0: // nsIXMLHttpRequest event types, supported only by XHR. michael@0: "readystatechange", michael@0: "loadend", michael@0: }; michael@0: michael@0: static_assert(MOZ_ARRAY_LENGTH(sEventStrings) == STRING_COUNT, michael@0: "Bad string count!"); michael@0: michael@0: class MainThreadProxyRunnable : public MainThreadWorkerSyncRunnable michael@0: { michael@0: protected: michael@0: nsRefPtr mProxy; michael@0: michael@0: MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy) michael@0: : MainThreadWorkerSyncRunnable(aWorkerPrivate, aProxy->GetEventTarget()), michael@0: mProxy(aProxy) michael@0: { michael@0: MOZ_ASSERT(aProxy); michael@0: } michael@0: michael@0: virtual ~MainThreadProxyRunnable() michael@0: { } michael@0: }; michael@0: michael@0: class XHRUnpinRunnable MOZ_FINAL : public MainThreadWorkerControlRunnable michael@0: { michael@0: XMLHttpRequest* mXMLHttpRequestPrivate; michael@0: michael@0: public: michael@0: XHRUnpinRunnable(WorkerPrivate* aWorkerPrivate, michael@0: XMLHttpRequest* aXHRPrivate) michael@0: : MainThreadWorkerControlRunnable(aWorkerPrivate), michael@0: mXMLHttpRequestPrivate(aXHRPrivate) michael@0: { michael@0: MOZ_ASSERT(aXHRPrivate); michael@0: } michael@0: michael@0: private: michael@0: ~XHRUnpinRunnable() michael@0: { } michael@0: michael@0: bool michael@0: WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) michael@0: { michael@0: mXMLHttpRequestPrivate->Unpin(); michael@0: michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: class AsyncTeardownRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: nsRefPtr mProxy; michael@0: michael@0: public: michael@0: AsyncTeardownRunnable(Proxy* aProxy) michael@0: : mProxy(aProxy) michael@0: { michael@0: MOZ_ASSERT(aProxy); michael@0: } michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: private: michael@0: ~AsyncTeardownRunnable() michael@0: { } michael@0: michael@0: NS_IMETHOD michael@0: Run() MOZ_OVERRIDE michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: mProxy->Teardown(); michael@0: mProxy = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: class LoadStartDetectionRunnable MOZ_FINAL : public nsRunnable, michael@0: public nsIDOMEventListener michael@0: { michael@0: WorkerPrivate* mWorkerPrivate; michael@0: nsRefPtr mProxy; michael@0: nsRefPtr mXHR; michael@0: XMLHttpRequest* mXMLHttpRequestPrivate; michael@0: nsString mEventType; michael@0: uint32_t mChannelId; michael@0: bool mReceivedLoadStart; michael@0: michael@0: class ProxyCompleteRunnable MOZ_FINAL : public MainThreadProxyRunnable michael@0: { michael@0: XMLHttpRequest* mXMLHttpRequestPrivate; michael@0: uint32_t mChannelId; michael@0: michael@0: public: michael@0: ProxyCompleteRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: XMLHttpRequest* aXHRPrivate, uint32_t aChannelId) michael@0: : MainThreadProxyRunnable(aWorkerPrivate, aProxy), michael@0: mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(aChannelId) michael@0: { } michael@0: michael@0: private: michael@0: ~ProxyCompleteRunnable() michael@0: { } michael@0: michael@0: virtual bool michael@0: WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE michael@0: { michael@0: if (mChannelId != mProxy->mOuterChannelId) { michael@0: // Threads raced, this event is now obsolete. michael@0: return true; michael@0: } michael@0: michael@0: if (mSyncLoopTarget) { michael@0: aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, true); michael@0: } michael@0: michael@0: mXMLHttpRequestPrivate->Unpin(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: NS_IMETHOD michael@0: Cancel() MOZ_OVERRIDE michael@0: { michael@0: // This must run! michael@0: nsresult rv = MainThreadProxyRunnable::Cancel(); michael@0: nsresult rv2 = Run(); michael@0: return NS_FAILED(rv) ? rv : rv2; michael@0: } michael@0: }; michael@0: michael@0: public: michael@0: LoadStartDetectionRunnable(Proxy* aProxy, XMLHttpRequest* aXHRPrivate) michael@0: : mWorkerPrivate(aProxy->mWorkerPrivate), mProxy(aProxy), mXHR(aProxy->mXHR), michael@0: mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(mProxy->mInnerChannelId), michael@0: mReceivedLoadStart(false) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: mEventType.AssignWithConversion(sEventStrings[STRING_loadstart]); michael@0: } michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: NS_DECL_NSIRUNNABLE michael@0: NS_DECL_NSIDOMEVENTLISTENER michael@0: michael@0: bool michael@0: RegisterAndDispatch() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (NS_FAILED(mXHR->AddEventListener(mEventType, this, false, false, 2))) { michael@0: NS_WARNING("Failed to add event listener!"); michael@0: return false; michael@0: } michael@0: michael@0: return NS_SUCCEEDED(NS_DispatchToCurrentThread(this)); michael@0: } michael@0: michael@0: private: michael@0: ~LoadStartDetectionRunnable() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: } michael@0: }; michael@0: michael@0: class EventRunnable MOZ_FINAL : public MainThreadProxyRunnable michael@0: { michael@0: nsString mType; michael@0: nsString mResponseType; michael@0: JSAutoStructuredCloneBuffer mResponseBuffer; michael@0: nsTArray > mClonedObjects; michael@0: JS::Heap mResponse; michael@0: nsString mResponseText; michael@0: nsCString mStatusText; michael@0: uint64_t mLoaded; michael@0: uint64_t mTotal; michael@0: uint32_t mEventStreamId; michael@0: uint32_t mStatus; michael@0: uint16_t mReadyState; michael@0: bool mUploadEvent; michael@0: bool mProgressEvent; michael@0: bool mLengthComputable; michael@0: nsresult mResponseTextResult; michael@0: nsresult mStatusResult; michael@0: nsresult mResponseResult; michael@0: michael@0: public: michael@0: class StateDataAutoRooter : private JS::CustomAutoRooter michael@0: { michael@0: XMLHttpRequest::StateData* mStateData; michael@0: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER michael@0: michael@0: public: michael@0: explicit StateDataAutoRooter(JSContext* aCx, XMLHttpRequest::StateData* aData michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM) michael@0: : CustomAutoRooter(aCx), mStateData(aData) michael@0: { michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: } michael@0: michael@0: private: michael@0: virtual void trace(JSTracer* aTrc) michael@0: { michael@0: JS_CallHeapValueTracer(aTrc, &mStateData->mResponse, michael@0: "XMLHttpRequest::StateData::mResponse"); michael@0: } michael@0: }; michael@0: michael@0: EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType, michael@0: bool aLengthComputable, uint64_t aLoaded, uint64_t aTotal) michael@0: : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType), michael@0: mResponse(JSVAL_VOID), mLoaded(aLoaded), mTotal(aTotal), michael@0: mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0), michael@0: mUploadEvent(aUploadEvent), mProgressEvent(true), michael@0: mLengthComputable(aLengthComputable), mResponseTextResult(NS_OK), michael@0: mStatusResult(NS_OK), mResponseResult(NS_OK) michael@0: { } michael@0: michael@0: EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType) michael@0: : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType), michael@0: mResponse(JSVAL_VOID), mLoaded(0), mTotal(0), michael@0: mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0), michael@0: mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0), michael@0: mResponseTextResult(NS_OK), mStatusResult(NS_OK), mResponseResult(NS_OK) michael@0: { } michael@0: michael@0: private: michael@0: ~EventRunnable() michael@0: { } michael@0: michael@0: virtual bool michael@0: PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE; michael@0: michael@0: virtual bool michael@0: WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE; michael@0: }; michael@0: michael@0: class WorkerThreadProxySyncRunnable : public nsRunnable michael@0: { michael@0: protected: michael@0: WorkerPrivate* mWorkerPrivate; michael@0: nsRefPtr mProxy; michael@0: nsCOMPtr mSyncLoopTarget; michael@0: michael@0: private: michael@0: class ResponseRunnable MOZ_FINAL: public MainThreadStopSyncLoopRunnable michael@0: { michael@0: nsRefPtr mProxy; michael@0: nsresult mErrorCode; michael@0: michael@0: public: michael@0: ResponseRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: nsresult aErrorCode) michael@0: : MainThreadStopSyncLoopRunnable(aWorkerPrivate, aProxy->GetEventTarget(), michael@0: NS_SUCCEEDED(aErrorCode)), michael@0: mProxy(aProxy), mErrorCode(aErrorCode) michael@0: { michael@0: MOZ_ASSERT(aProxy); michael@0: } michael@0: michael@0: private: michael@0: ~ResponseRunnable() michael@0: { } michael@0: michael@0: virtual void michael@0: MaybeSetException(JSContext* aCx) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_FAILED(mErrorCode)); michael@0: michael@0: Throw(aCx, mErrorCode); michael@0: } michael@0: }; michael@0: michael@0: public: michael@0: WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy) michael@0: : mWorkerPrivate(aWorkerPrivate), mProxy(aProxy) michael@0: { michael@0: MOZ_ASSERT(aWorkerPrivate); michael@0: MOZ_ASSERT(aProxy); michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: } michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: bool michael@0: Dispatch(JSContext* aCx) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: AutoSyncLoopHolder syncLoop(mWorkerPrivate); michael@0: mSyncLoopTarget = syncLoop.EventTarget(); michael@0: michael@0: if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { michael@0: JS_ReportError(aCx, "Failed to dispatch to main thread!"); michael@0: return false; michael@0: } michael@0: michael@0: return syncLoop.Run(); michael@0: } michael@0: michael@0: protected: michael@0: virtual ~WorkerThreadProxySyncRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() = 0; michael@0: michael@0: private: michael@0: NS_DECL_NSIRUNNABLE michael@0: }; michael@0: michael@0: class SyncTeardownRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable michael@0: { michael@0: public: michael@0: SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy) michael@0: { } michael@0: michael@0: private: michael@0: ~SyncTeardownRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE michael@0: { michael@0: mProxy->Teardown(); michael@0: MOZ_ASSERT(!mProxy->mSyncLoopTarget); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: class SetBackgroundRequestRunnable MOZ_FINAL : michael@0: public WorkerThreadProxySyncRunnable michael@0: { michael@0: bool mValue; michael@0: michael@0: public: michael@0: SetBackgroundRequestRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: bool aValue) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mValue(aValue) michael@0: { } michael@0: michael@0: private: michael@0: ~SetBackgroundRequestRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE michael@0: { michael@0: return mProxy->mXHR->SetMozBackgroundRequest(mValue); michael@0: } michael@0: }; michael@0: michael@0: class SetWithCredentialsRunnable MOZ_FINAL : michael@0: public WorkerThreadProxySyncRunnable michael@0: { michael@0: bool mValue; michael@0: michael@0: public: michael@0: SetWithCredentialsRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: bool aValue) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mValue(aValue) michael@0: { } michael@0: michael@0: private: michael@0: ~SetWithCredentialsRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE michael@0: { michael@0: return mProxy->mXHR->SetWithCredentials(mValue); michael@0: } michael@0: }; michael@0: michael@0: class SetResponseTypeRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable michael@0: { michael@0: nsString mResponseType; michael@0: michael@0: public: michael@0: SetResponseTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: const nsAString& aResponseType) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), michael@0: mResponseType(aResponseType) michael@0: { } michael@0: michael@0: void michael@0: GetResponseType(nsAString& aResponseType) michael@0: { michael@0: aResponseType.Assign(mResponseType); michael@0: } michael@0: michael@0: private: michael@0: ~SetResponseTypeRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE michael@0: { michael@0: nsresult rv = mProxy->mXHR->SetResponseType(mResponseType); michael@0: mResponseType.Truncate(); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = mProxy->mXHR->GetResponseType(mResponseType); michael@0: } michael@0: return rv; michael@0: } michael@0: }; michael@0: michael@0: class SetTimeoutRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable michael@0: { michael@0: uint32_t mTimeout; michael@0: michael@0: public: michael@0: SetTimeoutRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: uint32_t aTimeout) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mTimeout(aTimeout) michael@0: { } michael@0: michael@0: private: michael@0: ~SetTimeoutRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE michael@0: { michael@0: return mProxy->mXHR->SetTimeout(mTimeout); michael@0: } michael@0: }; michael@0: michael@0: class AbortRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable michael@0: { michael@0: public: michael@0: AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy) michael@0: { } michael@0: michael@0: private: michael@0: ~AbortRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE; michael@0: }; michael@0: michael@0: class GetAllResponseHeadersRunnable MOZ_FINAL : michael@0: public WorkerThreadProxySyncRunnable michael@0: { michael@0: nsCString& mResponseHeaders; michael@0: michael@0: public: michael@0: GetAllResponseHeadersRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: nsCString& aResponseHeaders) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), michael@0: mResponseHeaders(aResponseHeaders) michael@0: { } michael@0: michael@0: private: michael@0: ~GetAllResponseHeadersRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE michael@0: { michael@0: mProxy->mXHR->GetAllResponseHeaders(mResponseHeaders); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: class GetResponseHeaderRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable michael@0: { michael@0: const nsCString mHeader; michael@0: nsCString& mValue; michael@0: michael@0: public: michael@0: GetResponseHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: const nsACString& aHeader, nsCString& aValue) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mHeader(aHeader), michael@0: mValue(aValue) michael@0: { } michael@0: michael@0: private: michael@0: ~GetResponseHeaderRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE michael@0: { michael@0: return mProxy->mXHR->GetResponseHeader(mHeader, mValue); michael@0: } michael@0: }; michael@0: michael@0: class OpenRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable michael@0: { michael@0: nsCString mMethod; michael@0: nsString mURL; michael@0: Optional mUser; michael@0: nsString mUserStr; michael@0: Optional mPassword; michael@0: nsString mPasswordStr; michael@0: bool mBackgroundRequest; michael@0: bool mWithCredentials; michael@0: uint32_t mTimeout; michael@0: michael@0: public: michael@0: OpenRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: const nsACString& aMethod, const nsAString& aURL, michael@0: const Optional& aUser, michael@0: const Optional& aPassword, michael@0: bool aBackgroundRequest, bool aWithCredentials, michael@0: uint32_t aTimeout) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMethod(aMethod), michael@0: mURL(aURL), mBackgroundRequest(aBackgroundRequest), michael@0: mWithCredentials(aWithCredentials), mTimeout(aTimeout) michael@0: { michael@0: if (aUser.WasPassed()) { michael@0: mUserStr = aUser.Value(); michael@0: mUser = &mUserStr; michael@0: } michael@0: if (aPassword.WasPassed()) { michael@0: mPasswordStr = aPassword.Value(); michael@0: mPassword = &mPasswordStr; michael@0: } michael@0: } michael@0: michael@0: private: michael@0: ~OpenRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE michael@0: { michael@0: WorkerPrivate* oldWorker = mProxy->mWorkerPrivate; michael@0: mProxy->mWorkerPrivate = mWorkerPrivate; michael@0: michael@0: nsresult rv = MainThreadRunInternal(); michael@0: michael@0: mProxy->mWorkerPrivate = oldWorker; michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: MainThreadRunInternal(); michael@0: }; michael@0: michael@0: class SendRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable michael@0: { michael@0: nsString mStringBody; michael@0: JSAutoStructuredCloneBuffer mBody; michael@0: nsTArray > mClonedObjects; michael@0: nsCOMPtr mSyncLoopTarget; michael@0: bool mHasUploadListeners; michael@0: michael@0: public: michael@0: SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: const nsAString& aStringBody, JSAutoStructuredCloneBuffer&& aBody, michael@0: nsTArray>& aClonedObjects, michael@0: nsIEventTarget* aSyncLoopTarget, bool aHasUploadListeners) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy) michael@0: , mStringBody(aStringBody) michael@0: , mBody(Move(aBody)) michael@0: , mSyncLoopTarget(aSyncLoopTarget) michael@0: , mHasUploadListeners(aHasUploadListeners) michael@0: { michael@0: mClonedObjects.SwapElements(aClonedObjects); michael@0: } michael@0: michael@0: private: michael@0: ~SendRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE; michael@0: }; michael@0: michael@0: class SetRequestHeaderRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable michael@0: { michael@0: nsCString mHeader; michael@0: nsCString mValue; michael@0: michael@0: public: michael@0: SetRequestHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: const nsACString& aHeader, const nsACString& aValue) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mHeader(aHeader), michael@0: mValue(aValue) michael@0: { } michael@0: michael@0: private: michael@0: ~SetRequestHeaderRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE michael@0: { michael@0: return mProxy->mXHR->SetRequestHeader(mHeader, mValue); michael@0: } michael@0: }; michael@0: michael@0: class OverrideMimeTypeRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable michael@0: { michael@0: nsString mMimeType; michael@0: michael@0: public: michael@0: OverrideMimeTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, michael@0: const nsAString& aMimeType) michael@0: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMimeType(aMimeType) michael@0: { } michael@0: michael@0: private: michael@0: ~OverrideMimeTypeRunnable() michael@0: { } michael@0: michael@0: virtual nsresult michael@0: MainThreadRun() MOZ_OVERRIDE michael@0: { michael@0: mProxy->mXHR->OverrideMimeType(mMimeType); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: class AutoUnpinXHR michael@0: { michael@0: XMLHttpRequest* mXMLHttpRequestPrivate; michael@0: michael@0: public: michael@0: AutoUnpinXHR(XMLHttpRequest* aXMLHttpRequestPrivate) michael@0: : mXMLHttpRequestPrivate(aXMLHttpRequestPrivate) michael@0: { michael@0: MOZ_ASSERT(aXMLHttpRequestPrivate); michael@0: } michael@0: michael@0: ~AutoUnpinXHR() michael@0: { michael@0: if (mXMLHttpRequestPrivate) { michael@0: mXMLHttpRequestPrivate->Unpin(); michael@0: } michael@0: } michael@0: michael@0: void Clear() michael@0: { michael@0: mXMLHttpRequestPrivate = nullptr; michael@0: } michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: bool michael@0: Proxy::Init() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: MOZ_ASSERT(mWorkerPrivate); michael@0: michael@0: if (mXHR) { michael@0: return true; michael@0: } michael@0: michael@0: nsPIDOMWindow* ownerWindow = mWorkerPrivate->GetWindow(); michael@0: if (ownerWindow) { michael@0: ownerWindow = ownerWindow->GetOuterWindow(); michael@0: if (!ownerWindow) { michael@0: NS_ERROR("No outer window?!"); michael@0: return false; michael@0: } michael@0: michael@0: nsPIDOMWindow* innerWindow = ownerWindow->GetCurrentInnerWindow(); michael@0: if (mWorkerPrivate->GetWindow() != innerWindow) { michael@0: NS_WARNING("Window has navigated, cannot create XHR here."); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: mXHR = new nsXMLHttpRequest(); michael@0: michael@0: nsCOMPtr global = do_QueryInterface(ownerWindow); michael@0: if (NS_FAILED(mXHR->Init(mWorkerPrivate->GetPrincipal(), michael@0: mWorkerPrivate->GetScriptContext(), michael@0: global, mWorkerPrivate->GetBaseURI()))) { michael@0: mXHR = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: mXHR->SetParameters(mMozAnon, mMozSystem); michael@0: michael@0: if (NS_FAILED(mXHR->GetUpload(getter_AddRefs(mXHRUpload)))) { michael@0: mXHR = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: if (!AddRemoveEventListeners(false, true)) { michael@0: mXHRUpload = nullptr; michael@0: mXHR = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: Proxy::Teardown() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (mXHR) { michael@0: Reset(); michael@0: michael@0: // NB: We are intentionally dropping events coming from xhr.abort on the michael@0: // floor. michael@0: AddRemoveEventListeners(false, false); michael@0: mXHR->Abort(); michael@0: michael@0: if (mOutstandingSendCount) { michael@0: nsRefPtr runnable = michael@0: new XHRUnpinRunnable(mWorkerPrivate, mXMLHttpRequestPrivate); michael@0: if (!runnable->Dispatch(nullptr)) { michael@0: NS_RUNTIMEABORT("We're going to hang at shutdown anyways."); michael@0: } michael@0: michael@0: if (mSyncLoopTarget) { michael@0: // We have an unclosed sync loop. Fix that now. michael@0: nsRefPtr runnable = michael@0: new MainThreadStopSyncLoopRunnable(mWorkerPrivate, michael@0: mSyncLoopTarget.forget(), michael@0: false); michael@0: if (!runnable->Dispatch(nullptr)) { michael@0: NS_RUNTIMEABORT("We're going to hang at shutdown anyways."); michael@0: } michael@0: } michael@0: michael@0: mWorkerPrivate = nullptr; michael@0: mOutstandingSendCount = 0; michael@0: } michael@0: michael@0: mXHRUpload = nullptr; michael@0: mXHR = nullptr; michael@0: } michael@0: michael@0: MOZ_ASSERT(!mWorkerPrivate); michael@0: MOZ_ASSERT(!mSyncLoopTarget); michael@0: } michael@0: michael@0: bool michael@0: Proxy::AddRemoveEventListeners(bool aUpload, bool aAdd) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: NS_ASSERTION(!aUpload || michael@0: (mUploadEventListenersAttached && !aAdd) || michael@0: (!mUploadEventListenersAttached && aAdd), michael@0: "Messed up logic for upload listeners!"); michael@0: michael@0: nsCOMPtr target = michael@0: aUpload ? michael@0: do_QueryInterface(mXHRUpload) : michael@0: do_QueryInterface(static_cast(mXHR.get())); michael@0: NS_ASSERTION(target, "This should never fail!"); michael@0: michael@0: uint32_t lastEventType = aUpload ? STRING_LAST_EVENTTARGET : STRING_LAST_XHR; michael@0: michael@0: nsAutoString eventType; michael@0: for (uint32_t index = 0; index <= lastEventType; index++) { michael@0: eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]); michael@0: if (aAdd) { michael@0: if (NS_FAILED(target->AddEventListener(eventType, this, false))) { michael@0: return false; michael@0: } michael@0: } michael@0: else if (NS_FAILED(target->RemoveEventListener(eventType, this, false))) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (aUpload) { michael@0: mUploadEventListenersAttached = aAdd; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(Proxy, nsIDOMEventListener) michael@0: michael@0: NS_IMETHODIMP michael@0: Proxy::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (!mWorkerPrivate || !mXMLHttpRequestPrivate) { michael@0: NS_ERROR("Shouldn't get here!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsString type; michael@0: if (NS_FAILED(aEvent->GetType(type))) { michael@0: NS_WARNING("Failed to get event type!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr target; michael@0: if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target)))) { michael@0: NS_WARNING("Failed to get target!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr uploadTarget = do_QueryInterface(target); michael@0: nsCOMPtr progressEvent = do_QueryInterface(aEvent); michael@0: michael@0: nsRefPtr runnable; michael@0: michael@0: if (mInOpen && type.EqualsASCII(sEventStrings[STRING_readystatechange])) { michael@0: uint16_t readyState = 0; michael@0: if (NS_SUCCEEDED(mXHR->GetReadyState(&readyState)) && michael@0: readyState == nsIXMLHttpRequest::OPENED) { michael@0: mInnerEventStreamId++; michael@0: } michael@0: } michael@0: michael@0: if (progressEvent) { michael@0: bool lengthComputable; michael@0: uint64_t loaded, total; michael@0: if (NS_FAILED(progressEvent->GetLengthComputable(&lengthComputable)) || michael@0: NS_FAILED(progressEvent->GetLoaded(&loaded)) || michael@0: NS_FAILED(progressEvent->GetTotal(&total))) { michael@0: NS_WARNING("Bad progress event!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: runnable = new EventRunnable(this, !!uploadTarget, type, lengthComputable, michael@0: loaded, total); michael@0: } michael@0: else { michael@0: runnable = new EventRunnable(this, !!uploadTarget, type); michael@0: } michael@0: michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest ar(cx); michael@0: runnable->Dispatch(cx); michael@0: } michael@0: michael@0: if (!uploadTarget) { michael@0: if (type.EqualsASCII(sEventStrings[STRING_loadstart])) { michael@0: NS_ASSERTION(!mMainThreadSeenLoadStart, "Huh?!"); michael@0: mMainThreadSeenLoadStart = true; michael@0: } michael@0: else if (mMainThreadSeenLoadStart && michael@0: type.EqualsASCII(sEventStrings[STRING_loadend])) { michael@0: mMainThreadSeenLoadStart = false; michael@0: michael@0: nsRefPtr runnable = michael@0: new LoadStartDetectionRunnable(this, mXMLHttpRequestPrivate); michael@0: if (!runnable->RegisterAndDispatch()) { michael@0: NS_WARNING("Failed to dispatch LoadStartDetectionRunnable!"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadProxySyncRunnable, nsRunnable) michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(AsyncTeardownRunnable, nsRunnable) michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(LoadStartDetectionRunnable, nsRunnable, michael@0: nsIDOMEventListener) michael@0: michael@0: NS_IMETHODIMP michael@0: LoadStartDetectionRunnable::Run() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (NS_FAILED(mXHR->RemoveEventListener(mEventType, this, false))) { michael@0: NS_WARNING("Failed to remove event listener!"); michael@0: } michael@0: michael@0: if (!mReceivedLoadStart) { michael@0: if (mProxy->mOutstandingSendCount > 1) { michael@0: mProxy->mOutstandingSendCount--; michael@0: } else if (mProxy->mOutstandingSendCount == 1) { michael@0: mProxy->Reset(); michael@0: michael@0: nsRefPtr runnable = michael@0: new ProxyCompleteRunnable(mWorkerPrivate, mProxy, michael@0: mXMLHttpRequestPrivate, mChannelId); michael@0: if (runnable->Dispatch(nullptr)) { michael@0: mProxy->mWorkerPrivate = nullptr; michael@0: mProxy->mSyncLoopTarget = nullptr; michael@0: mProxy->mOutstandingSendCount--; michael@0: } michael@0: } michael@0: } michael@0: michael@0: mProxy = nullptr; michael@0: mXHR = nullptr; michael@0: mXMLHttpRequestPrivate = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LoadStartDetectionRunnable::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: nsString type; michael@0: if (NS_SUCCEEDED(aEvent->GetType(type))) { michael@0: MOZ_ASSERT(type == mEventType); michael@0: } michael@0: else { michael@0: NS_WARNING("Failed to get event type!"); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: mReceivedLoadStart = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: EventRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsRefPtr& xhr = mProxy->mXHR; michael@0: MOZ_ASSERT(xhr); michael@0: michael@0: if (NS_FAILED(xhr->GetResponseType(mResponseType))) { michael@0: MOZ_ASSERT(false, "This should never fail!"); michael@0: } michael@0: michael@0: mResponseTextResult = xhr->GetResponseText(mResponseText); michael@0: if (NS_SUCCEEDED(mResponseTextResult)) { michael@0: mResponseResult = mResponseTextResult; michael@0: if (mResponseText.IsVoid()) { michael@0: mResponse = JSVAL_NULL; michael@0: } michael@0: } michael@0: else { michael@0: JS::Rooted response(aCx); michael@0: mResponseResult = xhr->GetResponse(aCx, &response); michael@0: if (NS_SUCCEEDED(mResponseResult)) { michael@0: if (JSVAL_IS_UNIVERSAL(response)) { michael@0: mResponse = response; michael@0: } michael@0: else { michael@0: // Anything subject to GC must be cloned. michael@0: JSStructuredCloneCallbacks* callbacks = michael@0: aWorkerPrivate->IsChromeWorker() ? michael@0: ChromeWorkerStructuredCloneCallbacks(true) : michael@0: WorkerStructuredCloneCallbacks(true); michael@0: michael@0: nsTArray > clonedObjects; michael@0: michael@0: if (mResponseBuffer.write(aCx, response, callbacks, &clonedObjects)) { michael@0: mClonedObjects.SwapElements(clonedObjects); michael@0: } michael@0: else { michael@0: NS_WARNING("Failed to clone response!"); michael@0: mResponseResult = NS_ERROR_DOM_DATA_CLONE_ERR; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: mStatusResult = xhr->GetStatus(&mStatus); michael@0: michael@0: xhr->GetStatusText(mStatusText); michael@0: michael@0: mReadyState = xhr->ReadyState(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: EventRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) michael@0: { michael@0: if (mEventStreamId != mProxy->mOuterEventStreamId) { michael@0: // Threads raced, this event is now obsolete. michael@0: return true; michael@0: } michael@0: michael@0: if (!mProxy->mXMLHttpRequestPrivate) { michael@0: // Object was finalized, bail. michael@0: return true; michael@0: } michael@0: michael@0: if (mType.EqualsASCII(sEventStrings[STRING_loadstart])) { michael@0: if (mUploadEvent) { michael@0: mProxy->mSeenUploadLoadStart = true; michael@0: } michael@0: else { michael@0: mProxy->mSeenLoadStart = true; michael@0: } michael@0: } michael@0: else if (mType.EqualsASCII(sEventStrings[STRING_loadend])) { michael@0: if (mUploadEvent) { michael@0: mProxy->mSeenUploadLoadStart = false; michael@0: } michael@0: else { michael@0: mProxy->mSeenLoadStart = false; michael@0: } michael@0: } michael@0: else if (mType.EqualsASCII(sEventStrings[STRING_abort])) { michael@0: if ((mUploadEvent && !mProxy->mSeenUploadLoadStart) || michael@0: (!mUploadEvent && !mProxy->mSeenLoadStart)) { michael@0: // We've already dispatched premature abort events. michael@0: return true; michael@0: } michael@0: } michael@0: else if (mType.EqualsASCII(sEventStrings[STRING_readystatechange])) { michael@0: if (mReadyState == 4 && !mUploadEvent && !mProxy->mSeenLoadStart) { michael@0: // We've already dispatched premature abort events. michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (mProgressEvent) { michael@0: // Cache these for premature abort events. michael@0: if (mUploadEvent) { michael@0: mProxy->mLastUploadLengthComputable = mLengthComputable; michael@0: mProxy->mLastUploadLoaded = mLoaded; michael@0: mProxy->mLastUploadTotal = mTotal; michael@0: } michael@0: else { michael@0: mProxy->mLastLengthComputable = mLengthComputable; michael@0: mProxy->mLastLoaded = mLoaded; michael@0: mProxy->mLastTotal = mTotal; michael@0: } michael@0: } michael@0: michael@0: nsAutoPtr state(new XMLHttpRequest::StateData()); michael@0: StateDataAutoRooter rooter(aCx, state); michael@0: michael@0: state->mResponseTextResult = mResponseTextResult; michael@0: state->mResponseText = mResponseText; michael@0: michael@0: if (NS_SUCCEEDED(mResponseTextResult)) { michael@0: MOZ_ASSERT(JSVAL_IS_VOID(mResponse) || JSVAL_IS_NULL(mResponse)); michael@0: state->mResponseResult = mResponseTextResult; michael@0: state->mResponse = mResponse; michael@0: } michael@0: else { michael@0: state->mResponseResult = mResponseResult; michael@0: michael@0: if (NS_SUCCEEDED(mResponseResult)) { michael@0: if (mResponseBuffer.data()) { michael@0: MOZ_ASSERT(JSVAL_IS_VOID(mResponse)); michael@0: michael@0: JSAutoStructuredCloneBuffer responseBuffer(Move(mResponseBuffer)); michael@0: michael@0: JSStructuredCloneCallbacks* callbacks = michael@0: aWorkerPrivate->IsChromeWorker() ? michael@0: ChromeWorkerStructuredCloneCallbacks(false) : michael@0: WorkerStructuredCloneCallbacks(false); michael@0: michael@0: nsTArray > clonedObjects; michael@0: clonedObjects.SwapElements(mClonedObjects); michael@0: michael@0: JS::Rooted response(aCx); michael@0: if (!responseBuffer.read(aCx, &response, callbacks, &clonedObjects)) { michael@0: return false; michael@0: } michael@0: michael@0: state->mResponse = response; michael@0: } michael@0: else { michael@0: state->mResponse = mResponse; michael@0: } michael@0: } michael@0: } michael@0: michael@0: state->mStatusResult = mStatusResult; michael@0: state->mStatus = mStatus; michael@0: michael@0: state->mStatusText = mStatusText; michael@0: michael@0: state->mReadyState = mReadyState; michael@0: michael@0: XMLHttpRequest* xhr = mProxy->mXMLHttpRequestPrivate; michael@0: xhr->UpdateState(*state); michael@0: michael@0: if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) { michael@0: return true; michael@0: } michael@0: michael@0: JS::Rooted type(aCx, michael@0: JS_NewUCStringCopyN(aCx, mType.get(), mType.Length())); michael@0: if (!type) { michael@0: return false; michael@0: } michael@0: michael@0: nsXHREventTarget* target; michael@0: if (mUploadEvent) { michael@0: target = xhr->GetUploadObjectNoCreate(); michael@0: } michael@0: else { michael@0: target = xhr; michael@0: } michael@0: michael@0: MOZ_ASSERT(target); michael@0: michael@0: nsCOMPtr event; michael@0: if (mProgressEvent) { michael@0: NS_NewDOMProgressEvent(getter_AddRefs(event), target, nullptr, nullptr); michael@0: nsCOMPtr progress = do_QueryInterface(event); michael@0: michael@0: if (progress) { michael@0: progress->InitProgressEvent(mType, false, false, mLengthComputable, michael@0: mLoaded, mTotal); michael@0: } michael@0: } michael@0: else { michael@0: NS_NewDOMEvent(getter_AddRefs(event), target, nullptr, nullptr); michael@0: michael@0: if (event) { michael@0: event->InitEvent(mType, false, false); michael@0: } michael@0: } michael@0: michael@0: if (!event) { michael@0: return false; michael@0: } michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: target->DispatchDOMEvent(nullptr, event, nullptr, nullptr); michael@0: michael@0: // After firing the event set mResponse to JSVAL_NULL for chunked response michael@0: // types. michael@0: if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) { michael@0: xhr->NullResponseText(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WorkerThreadProxySyncRunnable::Run() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsCOMPtr tempTarget; michael@0: mSyncLoopTarget.swap(tempTarget); michael@0: michael@0: mProxy->mSyncEventResponseTarget.swap(tempTarget); michael@0: michael@0: nsresult rv = MainThreadRun(); michael@0: michael@0: nsRefPtr response = michael@0: new ResponseRunnable(mWorkerPrivate, mProxy, rv); michael@0: if (!response->Dispatch(nullptr)) { michael@0: MOZ_ASSERT(false, "Failed to dispatch response!"); michael@0: } michael@0: michael@0: mProxy->mSyncEventResponseTarget.swap(tempTarget); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: AbortRunnable::MainThreadRun() michael@0: { michael@0: mProxy->mInnerEventStreamId++; michael@0: michael@0: WorkerPrivate* oldWorker = mProxy->mWorkerPrivate; michael@0: mProxy->mWorkerPrivate = mWorkerPrivate; michael@0: michael@0: mProxy->mXHR->Abort(); michael@0: michael@0: mProxy->mWorkerPrivate = oldWorker; michael@0: michael@0: mProxy->Reset(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: OpenRunnable::MainThreadRunInternal() michael@0: { michael@0: if (!mProxy->Init()) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: if (mBackgroundRequest) { michael@0: rv = mProxy->mXHR->SetMozBackgroundRequest(mBackgroundRequest); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (mWithCredentials) { michael@0: rv = mProxy->mXHR->SetWithCredentials(mWithCredentials); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (mTimeout) { michael@0: rv = mProxy->mXHR->SetTimeout(mTimeout); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: MOZ_ASSERT(!mProxy->mInOpen); michael@0: mProxy->mInOpen = true; michael@0: michael@0: ErrorResult rv2; michael@0: mProxy->mXHR->Open(mMethod, mURL, true, mUser, mPassword, rv2); michael@0: michael@0: MOZ_ASSERT(mProxy->mInOpen); michael@0: mProxy->mInOpen = false; michael@0: michael@0: if (rv2.Failed()) { michael@0: return rv2.ErrorCode(); michael@0: } michael@0: michael@0: return mProxy->mXHR->SetResponseType(NS_LITERAL_STRING("text")); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: SendRunnable::MainThreadRun() michael@0: { michael@0: nsCOMPtr variant; michael@0: michael@0: if (mBody.data()) { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest ar(cx); michael@0: michael@0: nsIXPConnect* xpc = nsContentUtils::XPConnect(); michael@0: MOZ_ASSERT(xpc); michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: JSStructuredCloneCallbacks* callbacks = michael@0: mWorkerPrivate->IsChromeWorker() ? michael@0: ChromeWorkerStructuredCloneCallbacks(true) : michael@0: WorkerStructuredCloneCallbacks(true); michael@0: michael@0: JS::Rooted body(cx); michael@0: if (mBody.read(cx, &body, callbacks, &mClonedObjects)) { michael@0: if (NS_FAILED(xpc->JSValToVariant(cx, body, getter_AddRefs(variant)))) { michael@0: rv = NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: } michael@0: else { michael@0: rv = NS_ERROR_DOM_DATA_CLONE_ERR; michael@0: } michael@0: michael@0: mBody.clear(); michael@0: mClonedObjects.Clear(); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: nsCOMPtr wvariant = michael@0: do_CreateInstance(NS_VARIANT_CONTRACTID); michael@0: NS_ENSURE_TRUE(wvariant, NS_ERROR_UNEXPECTED); michael@0: michael@0: if (NS_FAILED(wvariant->SetAsAString(mStringBody))) { michael@0: MOZ_ASSERT(false, "This should never fail!"); michael@0: } michael@0: michael@0: variant = wvariant; michael@0: } michael@0: michael@0: MOZ_ASSERT(!mProxy->mWorkerPrivate); michael@0: mProxy->mWorkerPrivate = mWorkerPrivate; michael@0: michael@0: MOZ_ASSERT(!mProxy->mSyncLoopTarget); michael@0: mProxy->mSyncLoopTarget.swap(mSyncLoopTarget); michael@0: michael@0: if (mHasUploadListeners) { michael@0: NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!"); michael@0: if (!mProxy->AddRemoveEventListeners(true, true)) { michael@0: MOZ_ASSERT(false, "This should never fail!"); michael@0: } michael@0: } michael@0: michael@0: mProxy->mInnerChannelId++; michael@0: michael@0: nsresult rv = mProxy->mXHR->Send(variant); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mProxy->mOutstandingSendCount++; michael@0: michael@0: if (!mHasUploadListeners) { michael@0: NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!"); michael@0: if (!mProxy->AddRemoveEventListeners(true, true)) { michael@0: MOZ_ASSERT(false, "This should never fail!"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: XMLHttpRequest::XMLHttpRequest(WorkerPrivate* aWorkerPrivate) michael@0: : mWorkerPrivate(aWorkerPrivate), michael@0: mResponseType(XMLHttpRequestResponseType::Text), mTimeout(0), michael@0: mRooted(false), mBackgroundRequest(false), mWithCredentials(false), michael@0: mCanceled(false), mMozAnon(false), mMozSystem(false) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: SetIsDOMBinding(); michael@0: michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: michael@0: XMLHttpRequest::~XMLHttpRequest() michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: ReleaseProxy(XHRIsGoingAway); michael@0: michael@0: MOZ_ASSERT(!mRooted); michael@0: michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(XMLHttpRequest, nsXHREventTarget) michael@0: NS_IMPL_RELEASE_INHERITED(XMLHttpRequest, nsXHREventTarget) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XMLHttpRequest) michael@0: NS_INTERFACE_MAP_END_INHERITING(nsXHREventTarget) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequest) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequest, michael@0: nsXHREventTarget) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequest, michael@0: nsXHREventTarget) michael@0: tmp->ReleaseProxy(XHRIsGoingAway); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload) michael@0: tmp->mStateData.mResponse.setUndefined(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequest, michael@0: nsXHREventTarget) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mStateData.mResponse) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: JSObject* michael@0: XMLHttpRequest::WrapObject(JSContext* aCx) michael@0: { michael@0: return XMLHttpRequestBinding_workers::Wrap(aCx, this); michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: XMLHttpRequest::Constructor(const GlobalObject& aGlobal, michael@0: const MozXMLHttpRequestParameters& aParams, michael@0: ErrorResult& aRv) michael@0: { michael@0: JSContext* cx = aGlobal.GetContext(); michael@0: WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx); michael@0: MOZ_ASSERT(workerPrivate); michael@0: michael@0: nsRefPtr xhr = new XMLHttpRequest(workerPrivate); michael@0: michael@0: if (workerPrivate->XHRParamsAllowed()) { michael@0: if (aParams.mMozSystem) michael@0: xhr->mMozAnon = true; michael@0: else michael@0: xhr->mMozAnon = aParams.mMozAnon; michael@0: xhr->mMozSystem = aParams.mMozSystem; michael@0: } michael@0: michael@0: return xhr.forget(); michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::ReleaseProxy(ReleaseType aType) michael@0: { michael@0: // Can't assert that we're on the worker thread here because mWorkerPrivate michael@0: // may be gone. michael@0: michael@0: if (mProxy) { michael@0: if (aType == XHRIsGoingAway) { michael@0: // We're in a GC finalizer, so we can't do a sync call here (and we don't michael@0: // need to). michael@0: nsRefPtr runnable = michael@0: new AsyncTeardownRunnable(mProxy); michael@0: mProxy = nullptr; michael@0: michael@0: if (NS_FAILED(NS_DispatchToMainThread(runnable))) { michael@0: NS_ERROR("Failed to dispatch teardown runnable!"); michael@0: } michael@0: } else { michael@0: // This isn't necessary if the worker is going away or the XHR is going michael@0: // away. michael@0: if (aType == Default) { michael@0: // Don't let any more events run. michael@0: mProxy->mOuterEventStreamId++; michael@0: } michael@0: michael@0: // We need to make a sync call here. michael@0: nsRefPtr runnable = michael@0: new SyncTeardownRunnable(mWorkerPrivate, mProxy); michael@0: mProxy = nullptr; michael@0: michael@0: if (!runnable->Dispatch(nullptr)) { michael@0: NS_ERROR("Failed to dispatch teardown runnable!"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::MaybePin(ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mRooted) { michael@0: return; michael@0: } michael@0: michael@0: JSContext* cx = GetCurrentThreadJSContext(); michael@0: michael@0: if (!mWorkerPrivate->AddFeature(cx, this)) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: NS_ADDREF_THIS(); michael@0: michael@0: mRooted = true; michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::MaybeDispatchPrematureAbortEvents(ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: MOZ_ASSERT(mProxy); michael@0: michael@0: mStateData.mReadyState = 4; michael@0: michael@0: if (mProxy->mSeenUploadLoadStart) { michael@0: MOZ_ASSERT(mUpload); michael@0: michael@0: DispatchPrematureAbortEvent(mUpload, NS_LITERAL_STRING("abort"), true, michael@0: aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: DispatchPrematureAbortEvent(mUpload, NS_LITERAL_STRING("loadend"), true, michael@0: aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: mProxy->mSeenUploadLoadStart = false; michael@0: } michael@0: michael@0: if (mProxy->mSeenLoadStart) { michael@0: DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("readystatechange"), michael@0: false, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("abort"), false, aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("loadend"), false, michael@0: aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: mProxy->mSeenLoadStart = false; michael@0: } michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::DispatchPrematureAbortEvent(EventTarget* aTarget, michael@0: const nsAString& aEventType, michael@0: bool aUploadTarget, michael@0: ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: MOZ_ASSERT(aTarget); michael@0: michael@0: if (!mProxy) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr event; michael@0: if (aEventType.EqualsLiteral("readystatechange")) { michael@0: NS_NewDOMEvent(getter_AddRefs(event), aTarget, nullptr, nullptr); michael@0: michael@0: if (event) { michael@0: event->InitEvent(aEventType, false, false); michael@0: } michael@0: } michael@0: else { michael@0: NS_NewDOMProgressEvent(getter_AddRefs(event), aTarget, nullptr, nullptr); michael@0: michael@0: nsCOMPtr progress = do_QueryInterface(event); michael@0: if (progress) { michael@0: if (aUploadTarget) { michael@0: progress->InitProgressEvent(aEventType, false, false, michael@0: mProxy->mLastUploadLengthComputable, michael@0: mProxy->mLastUploadLoaded, michael@0: mProxy->mLastUploadTotal); michael@0: } michael@0: else { michael@0: progress->InitProgressEvent(aEventType, false, false, michael@0: mProxy->mLastLengthComputable, michael@0: mProxy->mLastLoaded, michael@0: mProxy->mLastTotal); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!event) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: aTarget->DispatchDOMEvent(nullptr, event, nullptr, nullptr); michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::Unpin() michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: MOZ_ASSERT(mRooted, "Mismatched calls to Unpin!"); michael@0: michael@0: JSContext* cx = GetCurrentThreadJSContext(); michael@0: michael@0: mWorkerPrivate->RemoveFeature(cx, this); michael@0: michael@0: mRooted = false; michael@0: michael@0: NS_RELEASE_THIS(); michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::SendInternal(const nsAString& aStringBody, michael@0: JSAutoStructuredCloneBuffer&& aBody, michael@0: nsTArray >& aClonedObjects, michael@0: ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: bool hasUploadListeners = mUpload ? mUpload->HasListeners() : false; michael@0: michael@0: MaybePin(aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: AutoUnpinXHR autoUnpin(this); michael@0: Maybe autoSyncLoop; michael@0: michael@0: nsCOMPtr syncLoopTarget; michael@0: bool isSyncXHR = mProxy->mIsSyncXHR; michael@0: if (isSyncXHR) { michael@0: autoSyncLoop.construct(mWorkerPrivate); michael@0: syncLoopTarget = autoSyncLoop.ref().EventTarget(); michael@0: } michael@0: michael@0: mProxy->mOuterChannelId++; michael@0: michael@0: JSContext* cx = mWorkerPrivate->GetJSContext(); michael@0: michael@0: nsRefPtr runnable = michael@0: new SendRunnable(mWorkerPrivate, mProxy, aStringBody, Move(aBody), michael@0: aClonedObjects, syncLoopTarget, hasUploadListeners); michael@0: if (!runnable->Dispatch(cx)) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: if (!isSyncXHR) { michael@0: autoUnpin.Clear(); michael@0: MOZ_ASSERT(autoSyncLoop.empty()); michael@0: return; michael@0: } michael@0: michael@0: autoUnpin.Clear(); michael@0: michael@0: if (!autoSyncLoop.ref().Run()) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: XMLHttpRequest::Notify(JSContext* aCx, Status aStatus) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: MOZ_ASSERT(mWorkerPrivate->GetJSContext() == aCx); michael@0: michael@0: if (aStatus >= Canceling && !mCanceled) { michael@0: mCanceled = true; michael@0: ReleaseProxy(WorkerIsGoingAway); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::Open(const nsACString& aMethod, const nsAString& aUrl, michael@0: bool aAsync, const Optional& aUser, michael@0: const Optional& aPassword, ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: if (mProxy) { michael@0: MaybeDispatchPrematureAbortEvents(aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: } michael@0: else { michael@0: mProxy = new Proxy(this, mMozAnon, mMozSystem); michael@0: } michael@0: michael@0: mProxy->mOuterEventStreamId++; michael@0: michael@0: nsRefPtr runnable = michael@0: new OpenRunnable(mWorkerPrivate, mProxy, aMethod, aUrl, aUser, aPassword, michael@0: mBackgroundRequest, mWithCredentials, michael@0: mTimeout); michael@0: michael@0: if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { michael@0: ReleaseProxy(); michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: mProxy->mIsSyncXHR = !aAsync; michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::SetRequestHeader(const nsACString& aHeader, michael@0: const nsACString& aValue, ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: if (!mProxy) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr runnable = michael@0: new SetRequestHeaderRunnable(mWorkerPrivate, mProxy, aHeader, aValue); michael@0: if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::SetTimeout(uint32_t aTimeout, ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: mTimeout = aTimeout; michael@0: michael@0: if (!mProxy) { michael@0: // Open may not have been called yet, in which case we'll handle the michael@0: // timeout in OpenRunnable. michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr runnable = michael@0: new SetTimeoutRunnable(mWorkerPrivate, mProxy, aTimeout); michael@0: if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::SetWithCredentials(bool aWithCredentials, ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: mWithCredentials = aWithCredentials; michael@0: michael@0: if (!mProxy) { michael@0: // Open may not have been called yet, in which case we'll handle the michael@0: // credentials in OpenRunnable. michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr runnable = michael@0: new SetWithCredentialsRunnable(mWorkerPrivate, mProxy, aWithCredentials); michael@0: if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::SetMozBackgroundRequest(bool aBackgroundRequest, michael@0: ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: mBackgroundRequest = aBackgroundRequest; michael@0: michael@0: if (!mProxy) { michael@0: // Open may not have been called yet, in which case we'll handle the michael@0: // background request in OpenRunnable. michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr runnable = michael@0: new SetBackgroundRequestRunnable(mWorkerPrivate, mProxy, michael@0: aBackgroundRequest); michael@0: if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: XMLHttpRequestUpload* michael@0: XMLHttpRequest::GetUpload(ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!mUpload) { michael@0: mUpload = XMLHttpRequestUpload::Create(this); michael@0: michael@0: if (!mUpload) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: return mUpload; michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::Send(ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: if (!mProxy) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: // Nothing to clone. michael@0: JSAutoStructuredCloneBuffer buffer; michael@0: nsTArray > clonedObjects; michael@0: michael@0: SendInternal(NullString(), Move(buffer), clonedObjects, aRv); michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::Send(const nsAString& aBody, ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: if (!mProxy) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: // Nothing to clone. michael@0: JSAutoStructuredCloneBuffer buffer; michael@0: nsTArray > clonedObjects; michael@0: michael@0: SendInternal(aBody, Move(buffer), clonedObjects, aRv); michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::Send(JS::Handle aBody, ErrorResult& aRv) michael@0: { michael@0: JSContext* cx = mWorkerPrivate->GetJSContext(); michael@0: michael@0: MOZ_ASSERT(aBody); michael@0: michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: if (!mProxy) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted valToClone(cx); michael@0: if (JS_IsArrayBufferObject(aBody) || JS_IsArrayBufferViewObject(aBody) || michael@0: file::GetDOMBlobFromJSObject(aBody)) { michael@0: valToClone.setObject(*aBody); michael@0: } michael@0: else { michael@0: JS::Rooted obj(cx, JS::ObjectValue(*aBody)); michael@0: JSString* bodyStr = JS::ToString(cx, obj); michael@0: if (!bodyStr) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: return; michael@0: } michael@0: valToClone.setString(bodyStr); michael@0: } michael@0: michael@0: JSStructuredCloneCallbacks* callbacks = michael@0: mWorkerPrivate->IsChromeWorker() ? michael@0: ChromeWorkerStructuredCloneCallbacks(false) : michael@0: WorkerStructuredCloneCallbacks(false); michael@0: michael@0: nsTArray > clonedObjects; michael@0: michael@0: JSAutoStructuredCloneBuffer buffer; michael@0: if (!buffer.write(cx, valToClone, callbacks, &clonedObjects)) { michael@0: aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); michael@0: return; michael@0: } michael@0: michael@0: SendInternal(EmptyString(), Move(buffer), clonedObjects, aRv); michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::Send(const ArrayBuffer& aBody, ErrorResult& aRv) michael@0: { michael@0: JS::Rooted obj(mWorkerPrivate->GetJSContext(), aBody.Obj()); michael@0: return Send(obj, aRv); michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::Send(const ArrayBufferView& aBody, ErrorResult& aRv) michael@0: { michael@0: JS::Rooted obj(mWorkerPrivate->GetJSContext(), aBody.Obj()); michael@0: return Send(obj, aRv); michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::SendAsBinary(const nsAString& aBody, ErrorResult& aRv) michael@0: { michael@0: NS_NOTYETIMPLEMENTED("Implement me!"); michael@0: aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); michael@0: return; michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::Abort(ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: } michael@0: michael@0: if (!mProxy) { michael@0: return; michael@0: } michael@0: michael@0: MaybeDispatchPrematureAbortEvents(aRv); michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: mProxy->mOuterEventStreamId++; michael@0: michael@0: nsRefPtr runnable = new AbortRunnable(mWorkerPrivate, mProxy); michael@0: if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::GetResponseHeader(const nsACString& aHeader, michael@0: nsACString& aResponseHeader, ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: if (!mProxy) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsCString responseHeader; michael@0: nsRefPtr runnable = michael@0: new GetResponseHeaderRunnable(mWorkerPrivate, mProxy, aHeader, michael@0: responseHeader); michael@0: if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: aResponseHeader = responseHeader; michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::GetAllResponseHeaders(nsACString& aResponseHeaders, michael@0: ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: if (!mProxy) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsCString responseHeaders; michael@0: nsRefPtr runnable = michael@0: new GetAllResponseHeadersRunnable(mWorkerPrivate, mProxy, responseHeaders); michael@0: if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: aResponseHeaders = responseHeaders; michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::OverrideMimeType(const nsAString& aMimeType, ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: // We're supposed to throw if the state is not OPENED or HEADERS_RECEIVED. We michael@0: // can detect OPENED really easily but we can't detect HEADERS_RECEIVED in a michael@0: // non-racy way until the XHR state machine actually runs on this thread michael@0: // (bug 671047). For now we're going to let this work only if the Send() michael@0: // method has not been called. michael@0: if (!mProxy || SendInProgress()) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr runnable = michael@0: new OverrideMimeTypeRunnable(mWorkerPrivate, mProxy, aMimeType); michael@0: if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::SetResponseType(XMLHttpRequestResponseType aResponseType, michael@0: ErrorResult& aRv) michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (mCanceled) { michael@0: aRv.Throw(UNCATCHABLE_EXCEPTION); michael@0: return; michael@0: } michael@0: michael@0: if (!mProxy || SendInProgress()) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: // "document" is fine for the main thread but not for a worker. Short-circuit michael@0: // that here. michael@0: if (aResponseType == XMLHttpRequestResponseType::Document) { michael@0: return; michael@0: } michael@0: michael@0: nsString responseType; michael@0: ConvertResponseTypeToString(aResponseType, responseType); michael@0: michael@0: nsRefPtr runnable = michael@0: new SetResponseTypeRunnable(mWorkerPrivate, mProxy, responseType); michael@0: if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: nsString acceptedResponseTypeString; michael@0: runnable->GetResponseType(acceptedResponseTypeString); michael@0: michael@0: mResponseType = ConvertStringToResponseType(acceptedResponseTypeString); michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::GetResponse(JSContext* /* unused */, michael@0: JS::MutableHandle aResponse, michael@0: ErrorResult& aRv) michael@0: { michael@0: if (NS_SUCCEEDED(mStateData.mResponseTextResult) && michael@0: JSVAL_IS_VOID(mStateData.mResponse)) { michael@0: MOZ_ASSERT(mStateData.mResponseText.Length()); michael@0: MOZ_ASSERT(NS_SUCCEEDED(mStateData.mResponseResult)); michael@0: michael@0: JSString* str = michael@0: JS_NewUCStringCopyN(mWorkerPrivate->GetJSContext(), michael@0: mStateData.mResponseText.get(), michael@0: mStateData.mResponseText.Length()); michael@0: if (!str) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: return; michael@0: } michael@0: michael@0: mStateData.mResponse = STRING_TO_JSVAL(str); michael@0: } michael@0: michael@0: JS::ExposeValueToActiveJS(mStateData.mResponse); michael@0: aRv = mStateData.mResponseResult; michael@0: aResponse.set(mStateData.mResponse); michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::GetResponseText(nsAString& aResponseText, ErrorResult& aRv) michael@0: { michael@0: aRv = mStateData.mResponseTextResult; michael@0: aResponseText = mStateData.mResponseText; michael@0: } michael@0: michael@0: void michael@0: XMLHttpRequest::UpdateState(const StateData& aStateData) michael@0: { michael@0: mStateData = aStateData; michael@0: }