summaryrefslogtreecommitdiffstats
path: root/dom/xhr/XMLHttpRequestWorker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xhr/XMLHttpRequestWorker.cpp')
-rw-r--r--dom/xhr/XMLHttpRequestWorker.cpp2455
1 files changed, 2455 insertions, 0 deletions
diff --git a/dom/xhr/XMLHttpRequestWorker.cpp b/dom/xhr/XMLHttpRequestWorker.cpp
new file mode 100644
index 000000000..f61383baf
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestWorker.cpp
@@ -0,0 +1,2455 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "XMLHttpRequestWorker.h"
+
+#include "nsIDOMEvent.h"
+#include "nsIDOMEventListener.h"
+#include "nsIRunnable.h"
+#include "nsIXMLHttpRequest.h"
+#include "nsIXPConnect.h"
+
+#include "jsfriendapi.h"
+#include "js/TracingAPI.h"
+#include "js/GCPolicyAPI.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/ProgressEvent.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/Telemetry.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsJSUtils.h"
+#include "nsThreadUtils.h"
+#include "nsVariant.h"
+
+#include "RuntimeService.h"
+#include "WorkerScope.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "XMLHttpRequestUpload.h"
+
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+/* static */ void
+XMLHttpRequestWorker::StateData::trace(JSTracer *aTrc)
+{
+ JS::TraceEdge(aTrc, &mResponse, "XMLHttpRequestWorker::StateData::mResponse");
+}
+
+/**
+ * XMLHttpRequest in workers
+ *
+ * XHR in workers is implemented by proxying calls/events/etc between the
+ * worker thread and an XMLHttpRequest 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 XMLHttpRequest) or the worker thread
+ * (the worker and XHR private objects).
+ *
+ * The main thread XHR is always operated in async mode, even for sync XHR
+ * in workers. Calls made on the worker thread are proxied to the main thread
+ * synchronously (meaning the worker thread is blocked until the call
+ * returns). Each proxied call spins up a sync queue, which captures any
+ * synchronously dispatched events and ensures that they run synchronously
+ * on the worker as well. Asynchronously dispatched events are posted to the
+ * worker thread to run asynchronously. Some of the XHR state is mirrored on
+ * the worker thread to avoid needing a cross-thread call on every property
+ * access.
+ *
+ * The XHR private is stored in the private slot of the XHR JSObject on the
+ * worker thread. It is destroyed when that JSObject is GCd. The private
+ * roots its JSObject while network activity is in progress. It also
+ * adds itself as a feature to the worker to give itself a chance to clean up
+ * if the worker goes away during an XHR call. It is important that the
+ * rooting and feature registration (collectively called pinning) happens at
+ * the proper times. If we pin for too long we can cause memory leaks or even
+ * shutdown hangs. If we don't pin for long enough we introduce a GC hazard.
+ *
+ * The XHR is pinned from the time Send is called to roughly the time loadend
+ * is received. There are some complications involved with Abort and XHR
+ * reuse. We maintain a counter on the main thread of how many times Send was
+ * called on this XHR, and we decrement the counter every time we receive a
+ * loadend event. When the counter reaches zero we dispatch a runnable to the
+ * worker thread to unpin the XHR. We only decrement the counter if the
+ * dispatch was successful, because the worker may no longer be accepting
+ * regular runnables. In the event that we reach Proxy::Teardown and there
+ * the outstanding Send count is still non-zero, we dispatch a control
+ * runnable which is guaranteed to run.
+ *
+ * NB: Some of this could probably be simplified now that we have the
+ * inner/outer channel ids.
+ */
+
+class Proxy final : public nsIDOMEventListener
+{
+public:
+ // Read on multiple threads.
+ WorkerPrivate* mWorkerPrivate;
+ XMLHttpRequestWorker* mXMLHttpRequestPrivate;
+
+ // XHR Params:
+ bool mMozAnon;
+ bool mMozSystem;
+
+ // Only touched on the main thread.
+ RefPtr<XMLHttpRequestMainThread> mXHR;
+ nsCOMPtr<nsIXMLHttpRequestUpload> mXHRUpload;
+ nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+ nsCOMPtr<nsIEventTarget> mSyncEventResponseTarget;
+ uint32_t mInnerEventStreamId;
+ uint32_t mInnerChannelId;
+ uint32_t mOutstandingSendCount;
+
+ // Only touched on the worker thread.
+ uint32_t mOuterEventStreamId;
+ uint32_t mOuterChannelId;
+ uint32_t mOpenCount;
+ uint64_t mLastLoaded;
+ uint64_t mLastTotal;
+ uint64_t mLastUploadLoaded;
+ uint64_t mLastUploadTotal;
+ bool mIsSyncXHR;
+ bool mLastLengthComputable;
+ bool mLastUploadLengthComputable;
+ bool mSeenLoadStart;
+ bool mSeenUploadLoadStart;
+
+ // Only touched on the main thread.
+ bool mUploadEventListenersAttached;
+ bool mMainThreadSeenLoadStart;
+ bool mInOpen;
+ bool mArrayBufferResponseWasTransferred;
+
+public:
+ Proxy(XMLHttpRequestWorker* aXHRPrivate, bool aMozAnon, bool aMozSystem)
+ : mWorkerPrivate(nullptr), mXMLHttpRequestPrivate(aXHRPrivate),
+ mMozAnon(aMozAnon), mMozSystem(aMozSystem),
+ mInnerEventStreamId(0), mInnerChannelId(0), mOutstandingSendCount(0),
+ mOuterEventStreamId(0), mOuterChannelId(0), mOpenCount(0), mLastLoaded(0),
+ mLastTotal(0), mLastUploadLoaded(0), mLastUploadTotal(0), mIsSyncXHR(false),
+ mLastLengthComputable(false), mLastUploadLengthComputable(false),
+ mSeenLoadStart(false), mSeenUploadLoadStart(false),
+ mUploadEventListenersAttached(false), mMainThreadSeenLoadStart(false),
+ mInOpen(false), mArrayBufferResponseWasTransferred(false)
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ bool
+ Init();
+
+ void
+ Teardown(bool aSendUnpin);
+
+ bool
+ AddRemoveEventListeners(bool aUpload, bool aAdd);
+
+ void
+ Reset()
+ {
+ AssertIsOnMainThread();
+
+ if (mUploadEventListenersAttached) {
+ AddRemoveEventListeners(true, false);
+ }
+ }
+
+ already_AddRefed<nsIEventTarget>
+ GetEventTarget()
+ {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIEventTarget> target = mSyncEventResponseTarget ?
+ mSyncEventResponseTarget :
+ mSyncLoopTarget;
+ return target.forget();
+ }
+
+private:
+ ~Proxy()
+ {
+ MOZ_ASSERT(!mXHR);
+ MOZ_ASSERT(!mXHRUpload);
+ MOZ_ASSERT(!mOutstandingSendCount);
+ }
+};
+
+class WorkerThreadProxySyncRunnable : public WorkerMainThreadRunnable
+{
+protected:
+ RefPtr<Proxy> mProxy;
+
+private:
+ // mErrorCode is set on the main thread by MainThreadRun and it's used to at
+ // the end of the Dispatch() to return the error code.
+ nsresult mErrorCode;
+
+public:
+ WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+ : WorkerMainThreadRunnable(aWorkerPrivate, NS_LITERAL_CSTRING("XHR"))
+ , mProxy(aProxy)
+ , mErrorCode(NS_OK)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aProxy);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ void
+ Dispatch(ErrorResult& aRv)
+ {
+ WorkerMainThreadRunnable::Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (NS_FAILED(mErrorCode)) {
+ aRv.Throw(mErrorCode);
+ }
+ }
+
+protected:
+ virtual ~WorkerThreadProxySyncRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) = 0;
+
+private:
+ virtual bool MainThreadRun() override;
+};
+
+class SendRunnable final
+ : public WorkerThreadProxySyncRunnable
+ , public StructuredCloneHolder
+{
+ nsString mStringBody;
+ nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+ bool mHasUploadListeners;
+
+public:
+ SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ const nsAString& aStringBody)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+ , StructuredCloneHolder(CloningSupported, TransferringNotSupported,
+ StructuredCloneScope::SameProcessDifferentThread)
+ , mStringBody(aStringBody)
+ , mHasUploadListeners(false)
+ {
+ }
+
+ void SetHaveUploadListeners(bool aHasUploadListeners)
+ {
+ mHasUploadListeners = aHasUploadListeners;
+ }
+
+ void SetSyncLoopTarget(nsIEventTarget* aSyncLoopTarget)
+ {
+ mSyncLoopTarget = aSyncLoopTarget;
+ }
+
+private:
+ ~SendRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override;
+};
+
+namespace {
+
+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
+};
+
+static_assert(STRING_LAST_XHR >= STRING_LAST_EVENTTARGET, "Bad string setup!");
+static_assert(STRING_LAST_XHR == STRING_COUNT - 1, "Bad string setup!");
+
+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",
+};
+
+static_assert(MOZ_ARRAY_LENGTH(sEventStrings) == STRING_COUNT,
+ "Bad string count!");
+
+class MainThreadProxyRunnable : public MainThreadWorkerSyncRunnable
+{
+protected:
+ RefPtr<Proxy> mProxy;
+
+ MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+ : MainThreadWorkerSyncRunnable(aWorkerPrivate, aProxy->GetEventTarget()),
+ mProxy(aProxy)
+ {
+ MOZ_ASSERT(aProxy);
+ }
+
+ virtual ~MainThreadProxyRunnable()
+ { }
+};
+
+class XHRUnpinRunnable final : public MainThreadWorkerControlRunnable
+{
+ XMLHttpRequestWorker* mXMLHttpRequestPrivate;
+
+public:
+ XHRUnpinRunnable(WorkerPrivate* aWorkerPrivate,
+ XMLHttpRequestWorker* aXHRPrivate)
+ : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ mXMLHttpRequestPrivate(aXHRPrivate)
+ {
+ MOZ_ASSERT(aXHRPrivate);
+ }
+
+private:
+ ~XHRUnpinRunnable()
+ { }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ if (mXMLHttpRequestPrivate->SendInProgress()) {
+ mXMLHttpRequestPrivate->Unpin();
+ }
+
+ return true;
+ }
+};
+
+class AsyncTeardownRunnable final : public Runnable
+{
+ RefPtr<Proxy> mProxy;
+
+public:
+ explicit AsyncTeardownRunnable(Proxy* aProxy)
+ : mProxy(aProxy)
+ {
+ MOZ_ASSERT(aProxy);
+ }
+
+private:
+ ~AsyncTeardownRunnable()
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ // This means the XHR was GC'd, so we can't be pinned, and we don't need to
+ // try to unpin.
+ mProxy->Teardown(/* aSendUnpin */ false);
+ mProxy = nullptr;
+
+ return NS_OK;
+ }
+};
+
+class LoadStartDetectionRunnable final : public Runnable,
+ public nsIDOMEventListener
+{
+ WorkerPrivate* mWorkerPrivate;
+ RefPtr<Proxy> mProxy;
+ RefPtr<XMLHttpRequest> mXHR;
+ XMLHttpRequestWorker* mXMLHttpRequestPrivate;
+ nsString mEventType;
+ uint32_t mChannelId;
+ bool mReceivedLoadStart;
+
+ class ProxyCompleteRunnable final : public MainThreadProxyRunnable
+ {
+ XMLHttpRequestWorker* mXMLHttpRequestPrivate;
+ uint32_t mChannelId;
+
+ public:
+ ProxyCompleteRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ XMLHttpRequestWorker* aXHRPrivate, uint32_t aChannelId)
+ : MainThreadProxyRunnable(aWorkerPrivate, aProxy),
+ mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(aChannelId)
+ { }
+
+ private:
+ ~ProxyCompleteRunnable()
+ { }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ if (mChannelId != mProxy->mOuterChannelId) {
+ // Threads raced, this event is now obsolete.
+ return true;
+ }
+
+ if (mSyncLoopTarget) {
+ aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, true);
+ }
+
+ if (mXMLHttpRequestPrivate->SendInProgress()) {
+ mXMLHttpRequestPrivate->Unpin();
+ }
+
+ return true;
+ }
+
+ nsresult
+ Cancel() override
+ {
+ // This must run!
+ nsresult rv = MainThreadProxyRunnable::Cancel();
+ nsresult rv2 = Run();
+ return NS_FAILED(rv) ? rv : rv2;
+ }
+ };
+
+public:
+ LoadStartDetectionRunnable(Proxy* aProxy, XMLHttpRequestWorker* aXHRPrivate)
+ : mWorkerPrivate(aProxy->mWorkerPrivate), mProxy(aProxy), mXHR(aProxy->mXHR),
+ mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(mProxy->mInnerChannelId),
+ mReceivedLoadStart(false)
+ {
+ AssertIsOnMainThread();
+ mEventType.AssignWithConversion(sEventStrings[STRING_loadstart]);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ 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));
+ }
+
+private:
+ ~LoadStartDetectionRunnable()
+ {
+ AssertIsOnMainThread();
+ }
+};
+
+class EventRunnable final : public MainThreadProxyRunnable
+ , public StructuredCloneHolder
+{
+ nsString mType;
+ nsString mResponseType;
+ JS::Heap<JS::Value> mResponse;
+ XMLHttpRequestStringSnapshot mResponseText;
+ nsString mResponseURL;
+ nsCString mStatusText;
+ uint64_t mLoaded;
+ uint64_t mTotal;
+ uint32_t mEventStreamId;
+ uint32_t mStatus;
+ uint16_t mReadyState;
+ bool mUploadEvent;
+ bool mProgressEvent;
+ bool mLengthComputable;
+ bool mUseCachedArrayBufferResponse;
+ nsresult mResponseTextResult;
+ nsresult mStatusResult;
+ nsresult mResponseResult;
+ // mScopeObj is used in PreDispatch only. We init it in our constructor, and
+ // reset() in PreDispatch, to ensure that it's not still linked into the
+ // runtime once we go off-thread.
+ JS::PersistentRooted<JSObject*> mScopeObj;
+
+public:
+ EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
+ bool aLengthComputable, uint64_t aLoaded, uint64_t aTotal,
+ JS::Handle<JSObject*> aScopeObj)
+ : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy),
+ StructuredCloneHolder(CloningSupported, TransferringNotSupported,
+ StructuredCloneScope::SameProcessDifferentThread),
+ mType(aType), mResponse(JS::UndefinedValue()), mLoaded(aLoaded),
+ mTotal(aTotal), mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0),
+ mReadyState(0), mUploadEvent(aUploadEvent), mProgressEvent(true),
+ mLengthComputable(aLengthComputable), mUseCachedArrayBufferResponse(false),
+ mResponseTextResult(NS_OK), mStatusResult(NS_OK), mResponseResult(NS_OK),
+ mScopeObj(RootingCx(), aScopeObj)
+ { }
+
+ EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
+ JS::Handle<JSObject*> aScopeObj)
+ : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy),
+ StructuredCloneHolder(CloningSupported, TransferringNotSupported,
+ StructuredCloneScope::SameProcessDifferentThread),
+ mType(aType), mResponse(JS::UndefinedValue()), mLoaded(0), mTotal(0),
+ mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0),
+ mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0),
+ mUseCachedArrayBufferResponse(false), mResponseTextResult(NS_OK),
+ mStatusResult(NS_OK), mResponseResult(NS_OK),
+ mScopeObj(RootingCx(), aScopeObj)
+ { }
+
+private:
+ ~EventRunnable()
+ { }
+
+ virtual bool
+ PreDispatch(WorkerPrivate* /* unused */) override final;
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+};
+
+class SyncTeardownRunnable final : public WorkerThreadProxySyncRunnable
+{
+public:
+ SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+ { }
+
+private:
+ ~SyncTeardownRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->Teardown(/* aSendUnpin */ true);
+ MOZ_ASSERT(!mProxy->mSyncLoopTarget);
+ }
+};
+
+class SetBackgroundRequestRunnable final :
+ public WorkerThreadProxySyncRunnable
+{
+ bool mValue;
+
+public:
+ SetBackgroundRequestRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ bool aValue)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+ , mValue(aValue)
+ { }
+
+private:
+ ~SetBackgroundRequestRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->SetMozBackgroundRequest(mValue, aRv);
+ }
+};
+
+class SetWithCredentialsRunnable final :
+ public WorkerThreadProxySyncRunnable
+{
+ bool mValue;
+
+public:
+ SetWithCredentialsRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ bool aValue)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+ , mValue(aValue)
+ { }
+
+private:
+ ~SetWithCredentialsRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->SetWithCredentials(mValue, aRv);
+ }
+};
+
+class SetResponseTypeRunnable final : public WorkerThreadProxySyncRunnable
+{
+ XMLHttpRequestResponseType mResponseType;
+
+public:
+ SetResponseTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ XMLHttpRequestResponseType aResponseType)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mResponseType(aResponseType)
+ { }
+
+ XMLHttpRequestResponseType
+ ResponseType()
+ {
+ return mResponseType;
+ }
+
+private:
+ ~SetResponseTypeRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->SetResponseType(mResponseType, aRv);
+ if (!aRv.Failed()) {
+ mResponseType = mProxy->mXHR->ResponseType();
+ }
+ }
+};
+
+class SetTimeoutRunnable final : public WorkerThreadProxySyncRunnable
+{
+ uint32_t mTimeout;
+
+public:
+ SetTimeoutRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ uint32_t aTimeout)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mTimeout(aTimeout)
+ { }
+
+private:
+ ~SetTimeoutRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->SetTimeout(mTimeout, aRv);
+ }
+};
+
+class AbortRunnable final : public WorkerThreadProxySyncRunnable
+{
+public:
+ AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+ { }
+
+private:
+ ~AbortRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override;
+};
+
+class GetAllResponseHeadersRunnable final :
+ public WorkerThreadProxySyncRunnable
+{
+ nsCString& mResponseHeaders;
+
+public:
+ GetAllResponseHeadersRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ nsCString& aResponseHeaders)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mResponseHeaders(aResponseHeaders)
+ { }
+
+private:
+ ~GetAllResponseHeadersRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->GetAllResponseHeaders(mResponseHeaders, aRv);
+ }
+};
+
+class GetResponseHeaderRunnable final : public WorkerThreadProxySyncRunnable
+{
+ const nsCString mHeader;
+ nsCString& mValue;
+
+public:
+ GetResponseHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ const nsACString& aHeader, nsCString& aValue)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mHeader(aHeader),
+ mValue(aValue)
+ { }
+
+private:
+ ~GetResponseHeaderRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->GetResponseHeader(mHeader, mValue, aRv);
+ }
+};
+
+class OpenRunnable final : public WorkerThreadProxySyncRunnable
+{
+ nsCString mMethod;
+ nsString mURL;
+ Optional<nsAString> mUser;
+ nsString mUserStr;
+ Optional<nsAString> mPassword;
+ nsString mPasswordStr;
+ bool mBackgroundRequest;
+ bool mWithCredentials;
+ uint32_t mTimeout;
+ XMLHttpRequestResponseType mResponseType;
+
+public:
+ OpenRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ const nsACString& aMethod, const nsAString& aURL,
+ const Optional<nsAString>& aUser,
+ const Optional<nsAString>& aPassword,
+ bool aBackgroundRequest, bool aWithCredentials,
+ uint32_t aTimeout, XMLHttpRequestResponseType aResponseType)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mMethod(aMethod),
+ mURL(aURL), mBackgroundRequest(aBackgroundRequest),
+ mWithCredentials(aWithCredentials), mTimeout(aTimeout),
+ mResponseType(aResponseType)
+ {
+ if (aUser.WasPassed()) {
+ mUserStr = aUser.Value();
+ mUser = &mUserStr;
+ }
+ if (aPassword.WasPassed()) {
+ mPasswordStr = aPassword.Value();
+ mPassword = &mPasswordStr;
+ }
+ }
+
+private:
+ ~OpenRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
+ mProxy->mWorkerPrivate = mWorkerPrivate;
+
+ aRv = MainThreadRunInternal();
+
+ mProxy->mWorkerPrivate = oldWorker;
+ }
+
+ nsresult
+ MainThreadRunInternal();
+};
+
+class SetRequestHeaderRunnable final : public WorkerThreadProxySyncRunnable
+{
+ nsCString mHeader;
+ nsCString mValue;
+
+public:
+ SetRequestHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ const nsACString& aHeader, const nsACString& aValue)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mHeader(aHeader),
+ mValue(aValue)
+ { }
+
+private:
+ ~SetRequestHeaderRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->SetRequestHeader(mHeader, mValue, aRv);
+ }
+};
+
+class OverrideMimeTypeRunnable final : public WorkerThreadProxySyncRunnable
+{
+ nsString mMimeType;
+
+public:
+ OverrideMimeTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ const nsAString& aMimeType)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mMimeType(aMimeType)
+ { }
+
+private:
+ ~OverrideMimeTypeRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->OverrideMimeType(mMimeType, aRv);
+ }
+};
+
+class AutoUnpinXHR
+{
+ XMLHttpRequestWorker* mXMLHttpRequestPrivate;
+
+public:
+ explicit AutoUnpinXHR(XMLHttpRequestWorker* aXMLHttpRequestPrivate)
+ : mXMLHttpRequestPrivate(aXMLHttpRequestPrivate)
+ {
+ MOZ_ASSERT(aXMLHttpRequestPrivate);
+ }
+
+ ~AutoUnpinXHR()
+ {
+ if (mXMLHttpRequestPrivate) {
+ mXMLHttpRequestPrivate->Unpin();
+ }
+ }
+
+ void Clear()
+ {
+ mXMLHttpRequestPrivate = nullptr;
+ }
+};
+
+} // namespace
+
+bool
+Proxy::Init()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mWorkerPrivate);
+
+ if (mXHR) {
+ return true;
+ }
+
+ nsPIDOMWindowInner* ownerWindow = mWorkerPrivate->GetWindow();
+ if (ownerWindow && !ownerWindow->IsCurrentInnerWindow()) {
+ NS_WARNING("Window has navigated, cannot create XHR here.");
+ return false;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(ownerWindow);
+
+ mXHR = new XMLHttpRequestMainThread();
+ mXHR->Construct(mWorkerPrivate->GetPrincipal(), global,
+ mWorkerPrivate->GetBaseURI(),
+ mWorkerPrivate->GetLoadGroup());
+
+ mXHR->SetParameters(mMozAnon, mMozSystem);
+
+ ErrorResult rv;
+ mXHRUpload = mXHR->GetUpload(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ mXHR = nullptr;
+ return false;
+ }
+
+ if (!AddRemoveEventListeners(false, true)) {
+ mXHR = nullptr;
+ mXHRUpload = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+void
+Proxy::Teardown(bool aSendUnpin)
+{
+ AssertIsOnMainThread();
+
+ if (mXHR) {
+ Reset();
+
+ // NB: We are intentionally dropping events coming from xhr.abort on the
+ // floor.
+ AddRemoveEventListeners(false, false);
+
+ ErrorResult rv;
+ mXHR->Abort(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+
+ if (mOutstandingSendCount) {
+ if (aSendUnpin) {
+ RefPtr<XHRUnpinRunnable> runnable =
+ new XHRUnpinRunnable(mWorkerPrivate, mXMLHttpRequestPrivate);
+ if (!runnable->Dispatch()) {
+ NS_RUNTIMEABORT("We're going to hang at shutdown anyways.");
+ }
+ }
+
+ if (mSyncLoopTarget) {
+ // We have an unclosed sync loop. Fix that now.
+ RefPtr<MainThreadStopSyncLoopRunnable> runnable =
+ new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
+ mSyncLoopTarget.forget(),
+ false);
+ if (!runnable->Dispatch()) {
+ NS_RUNTIMEABORT("We're going to hang at shutdown anyways.");
+ }
+ }
+
+ mOutstandingSendCount = 0;
+ }
+
+ mWorkerPrivate = nullptr;
+ mXHRUpload = nullptr;
+ mXHR = nullptr;
+ }
+
+ MOZ_ASSERT(!mWorkerPrivate);
+ MOZ_ASSERT(!mSyncLoopTarget);
+}
+
+bool
+Proxy::AddRemoveEventListeners(bool aUpload, bool aAdd)
+{
+ AssertIsOnMainThread();
+
+ NS_ASSERTION(!aUpload ||
+ (mUploadEventListenersAttached && !aAdd) ||
+ (!mUploadEventListenersAttached && aAdd),
+ "Messed up logic for upload listeners!");
+
+ nsCOMPtr<nsIDOMEventTarget> target =
+ aUpload ?
+ do_QueryInterface(mXHRUpload) :
+ do_QueryInterface(static_cast<nsIXMLHttpRequest*>(mXHR.get()));
+ NS_ASSERTION(target, "This should never fail!");
+
+ uint32_t lastEventType = aUpload ? STRING_LAST_EVENTTARGET : STRING_LAST_XHR;
+
+ nsAutoString eventType;
+ for (uint32_t 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_ISUPPORTS(Proxy, nsIDOMEventListener)
+
+NS_IMETHODIMP
+Proxy::HandleEvent(nsIDOMEvent* aEvent)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate || !mXMLHttpRequestPrivate) {
+ NS_ERROR("Shouldn't get here!");
+ return NS_OK;
+ }
+
+ nsString type;
+ if (NS_FAILED(aEvent->GetType(type))) {
+ NS_WARNING("Failed to get event type!");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDOMEventTarget> target;
+ if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target)))) {
+ NS_WARNING("Failed to get target!");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIXMLHttpRequestUpload> uploadTarget = do_QueryInterface(target);
+ ProgressEvent* progressEvent = aEvent->InternalDOMEvent()->AsProgressEvent();
+
+ if (mInOpen && type.EqualsASCII(sEventStrings[STRING_readystatechange])) {
+ uint16_t readyState = 0;
+ if (NS_SUCCEEDED(mXHR->GetReadyState(&readyState)) &&
+ readyState == nsIXMLHttpRequest::OPENED) {
+ mInnerEventStreamId++;
+ }
+ }
+
+ {
+ AutoSafeJSContext cx;
+ JSAutoRequest ar(cx);
+
+ JS::Rooted<JS::Value> value(cx);
+ if (!GetOrCreateDOMReflectorNoWrap(cx, mXHR, &value)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSObject*> scope(cx, &value.toObject());
+
+ RefPtr<EventRunnable> runnable;
+ if (progressEvent) {
+ runnable = new EventRunnable(this, !!uploadTarget, type,
+ progressEvent->LengthComputable(),
+ progressEvent->Loaded(),
+ progressEvent->Total(),
+ scope);
+ }
+ else {
+ runnable = new EventRunnable(this, !!uploadTarget, type, scope);
+ }
+
+ runnable->Dispatch();
+ }
+
+ if (!uploadTarget) {
+ if (type.EqualsASCII(sEventStrings[STRING_loadstart])) {
+ mMainThreadSeenLoadStart = true;
+ }
+ else if (mMainThreadSeenLoadStart &&
+ type.EqualsASCII(sEventStrings[STRING_loadend])) {
+ mMainThreadSeenLoadStart = false;
+
+ RefPtr<LoadStartDetectionRunnable> runnable =
+ new LoadStartDetectionRunnable(this, mXMLHttpRequestPrivate);
+ if (!runnable->RegisterAndDispatch()) {
+ NS_WARNING("Failed to dispatch LoadStartDetectionRunnable!");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(LoadStartDetectionRunnable, Runnable,
+ nsIDOMEventListener)
+
+NS_IMETHODIMP
+LoadStartDetectionRunnable::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();
+
+ RefPtr<ProxyCompleteRunnable> runnable =
+ new ProxyCompleteRunnable(mWorkerPrivate, mProxy,
+ mXMLHttpRequestPrivate, mChannelId);
+ if (runnable->Dispatch()) {
+ mProxy->mWorkerPrivate = nullptr;
+ mProxy->mSyncLoopTarget = nullptr;
+ mProxy->mOutstandingSendCount--;
+ }
+ }
+ }
+
+ mProxy = nullptr;
+ mXHR = nullptr;
+ mXMLHttpRequestPrivate = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadStartDetectionRunnable::HandleEvent(nsIDOMEvent* aEvent)
+{
+ AssertIsOnMainThread();
+
+#ifdef DEBUG
+ {
+ nsString type;
+ if (NS_SUCCEEDED(aEvent->GetType(type))) {
+ MOZ_ASSERT(type == mEventType);
+ }
+ else {
+ NS_WARNING("Failed to get event type!");
+ }
+ }
+#endif
+
+ mReceivedLoadStart = true;
+ return NS_OK;
+}
+
+bool
+EventRunnable::PreDispatch(WorkerPrivate* /* unused */)
+{
+ AssertIsOnMainThread();
+
+ AutoJSAPI jsapi;
+ DebugOnly<bool> ok = jsapi.Init(xpc::NativeGlobal(mScopeObj));
+ MOZ_ASSERT(ok);
+ JSContext* cx = jsapi.cx();
+ // Now keep the mScopeObj alive for the duration
+ JS::Rooted<JSObject*> scopeObj(cx, mScopeObj);
+ // And reset mScopeObj now, before we have a chance to run its destructor on
+ // some background thread.
+ mScopeObj.reset();
+
+ RefPtr<XMLHttpRequestMainThread>& xhr = mProxy->mXHR;
+ MOZ_ASSERT(xhr);
+
+ if (NS_FAILED(xhr->GetResponseType(mResponseType))) {
+ MOZ_ASSERT(false, "This should never fail!");
+ }
+
+ ErrorResult rv;
+ xhr->GetResponseText(mResponseText, rv);
+ mResponseTextResult = rv.StealNSResult();
+
+ if (NS_SUCCEEDED(mResponseTextResult)) {
+ mResponseResult = mResponseTextResult;
+ if (mResponseText.IsVoid()) {
+ mResponse.setNull();
+ }
+ }
+ else {
+ JS::Rooted<JS::Value> response(cx);
+ mResponseResult = xhr->GetResponse(cx, &response);
+ if (NS_SUCCEEDED(mResponseResult)) {
+ if (!response.isGCThing()) {
+ mResponse = response;
+ } else {
+ bool doClone = true;
+ JS::Rooted<JS::Value> transferable(cx);
+ JS::Rooted<JSObject*> obj(cx, response.isObjectOrNull() ?
+ response.toObjectOrNull() : nullptr);
+ if (obj && JS_IsArrayBufferObject(obj)) {
+ // Use cached response if the arraybuffer has been transfered.
+ if (mProxy->mArrayBufferResponseWasTransferred) {
+ MOZ_ASSERT(JS_IsDetachedArrayBufferObject(obj));
+ mUseCachedArrayBufferResponse = true;
+ doClone = false;
+ } else {
+ MOZ_ASSERT(!JS_IsDetachedArrayBufferObject(obj));
+ JS::AutoValueArray<1> argv(cx);
+ argv[0].set(response);
+ obj = JS_NewArrayObject(cx, argv);
+ if (obj) {
+ transferable.setObject(*obj);
+ // Only cache the response when the readyState is DONE.
+ if (xhr->ReadyState() == nsIXMLHttpRequest::DONE) {
+ mProxy->mArrayBufferResponseWasTransferred = true;
+ }
+ } else {
+ mResponseResult = NS_ERROR_OUT_OF_MEMORY;
+ doClone = false;
+ }
+ }
+ }
+
+ if (doClone) {
+ Write(cx, response, transferable, JS::CloneDataPolicy(), rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ NS_WARNING("Failed to clone response!");
+ mResponseResult = rv.StealNSResult();
+ mProxy->mArrayBufferResponseWasTransferred = false;
+ }
+ }
+ }
+ }
+ }
+
+ mStatusResult = xhr->GetStatus(&mStatus);
+
+ xhr->GetStatusText(mStatusText, rv);
+ MOZ_ASSERT(!rv.Failed());
+
+ mReadyState = xhr->ReadyState();
+
+ xhr->GetResponseURL(mResponseURL);
+
+ return true;
+}
+
+bool
+EventRunnable::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 {
+ if (!mProxy->mSeenLoadStart) {
+ // We've already dispatched premature abort events.
+ return true;
+ }
+ 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;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ JS::Rooted<UniquePtr<XMLHttpRequestWorker::StateData>> state(aCx, new XMLHttpRequestWorker::StateData());
+
+ state->mResponseTextResult = mResponseTextResult;
+
+ state->mResponseText = mResponseText;
+
+ if (NS_SUCCEEDED(mResponseTextResult)) {
+ MOZ_ASSERT(mResponse.isUndefined() || mResponse.isNull());
+ state->mResponseResult = mResponseTextResult;
+ state->mResponse = mResponse;
+ }
+ else {
+ state->mResponseResult = mResponseResult;
+
+ if (NS_SUCCEEDED(mResponseResult)) {
+ if (HasData()) {
+ MOZ_ASSERT(mResponse.isUndefined());
+
+ ErrorResult rv;
+ JS::Rooted<JS::Value> response(aCx);
+
+ GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(globalObj.GetAsSupports());
+
+ Read(global, aCx, &response, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ state->mResponse = response;
+ }
+ else {
+ state->mResponse = mResponse;
+ }
+ }
+ }
+
+ state->mStatusResult = mStatusResult;
+ state->mStatus = mStatus;
+
+ state->mStatusText = mStatusText;
+
+ state->mReadyState = mReadyState;
+
+ state->mResponseURL = mResponseURL;
+
+ XMLHttpRequestWorker* xhr = mProxy->mXMLHttpRequestPrivate;
+ xhr->UpdateState(*state.get(), mUseCachedArrayBufferResponse);
+
+ if (mType.EqualsASCII(sEventStrings[STRING_readystatechange])) {
+ if (mReadyState == 4 && !mUploadEvent && !mProxy->mSeenLoadStart) {
+ // We've already dispatched premature abort events.
+ return true;
+ }
+ }
+
+ if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) {
+ return true;
+ }
+
+ JS::Rooted<JSString*> type(aCx,
+ JS_NewUCStringCopyN(aCx, mType.get(), mType.Length()));
+ if (!type) {
+ return false;
+ }
+
+ XMLHttpRequestEventTarget* target;
+ if (mUploadEvent) {
+ target = xhr->GetUploadObjectNoCreate();
+ }
+ else {
+ target = xhr;
+ }
+
+ MOZ_ASSERT(target);
+
+ RefPtr<Event> event;
+ if (mProgressEvent) {
+ ProgressEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mLengthComputable = mLengthComputable;
+ init.mLoaded = mLoaded;
+ init.mTotal = mTotal;
+
+ event = ProgressEvent::Constructor(target, mType, init);
+ }
+ else {
+ event = NS_NewDOMEvent(target, nullptr, nullptr);
+
+ if (event) {
+ event->InitEvent(mType, false, false);
+ }
+ }
+
+ if (!event) {
+ return false;
+ }
+
+ event->SetTrusted(true);
+
+ target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+
+ // After firing the event set mResponse to JSVAL_NULL for chunked response
+ // types.
+ if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) {
+ xhr->NullResponseText();
+ }
+
+ return true;
+}
+
+bool
+WorkerThreadProxySyncRunnable::MainThreadRun()
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIEventTarget> tempTarget = mSyncLoopTarget;
+
+ mProxy->mSyncEventResponseTarget.swap(tempTarget);
+
+ ErrorResult rv;
+ RunOnMainThread(rv);
+ mErrorCode = rv.StealNSResult();
+
+ mProxy->mSyncEventResponseTarget.swap(tempTarget);
+
+ return true;
+}
+
+void
+AbortRunnable::RunOnMainThread(ErrorResult& aRv)
+{
+ mProxy->mInnerEventStreamId++;
+
+ WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
+ mProxy->mWorkerPrivate = mWorkerPrivate;
+
+ mProxy->mXHR->Abort(aRv);
+
+ mProxy->mWorkerPrivate = oldWorker;
+
+ mProxy->Reset();
+}
+
+nsresult
+OpenRunnable::MainThreadRunInternal()
+{
+ if (!mProxy->Init()) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ nsresult rv;
+
+ if (mBackgroundRequest) {
+ rv = mProxy->mXHR->SetMozBackgroundRequest(mBackgroundRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mWithCredentials) {
+ rv = mProxy->mXHR->SetWithCredentials(mWithCredentials);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mTimeout) {
+ rv = mProxy->mXHR->SetTimeout(mTimeout);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_ASSERT(!mProxy->mInOpen);
+ mProxy->mInOpen = true;
+
+ ErrorResult rv2;
+ mProxy->mXHR->Open(mMethod, mURL, true,
+ mUser.WasPassed() ? mUser.Value() : NullString(),
+ mPassword.WasPassed() ? mPassword.Value() : NullString(),
+ rv2);
+
+ MOZ_ASSERT(mProxy->mInOpen);
+ mProxy->mInOpen = false;
+
+ if (rv2.Failed()) {
+ return rv2.StealNSResult();
+ }
+
+ mProxy->mXHR->SetResponseType(mResponseType, rv2);
+ if (rv2.Failed()) {
+ return rv2.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+void
+SendRunnable::RunOnMainThread(ErrorResult& aRv)
+{
+ nsCOMPtr<nsIVariant> variant;
+
+ if (HasData()) {
+ AutoSafeJSContext cx;
+ JSAutoRequest ar(cx);
+
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc);
+
+ JS::Rooted<JSObject*> globalObject(cx, JS::CurrentGlobalOrNull(cx));
+ if (NS_WARN_IF(!globalObject)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsIGlobalObject> parent = xpc::NativeGlobal(globalObject);
+ if (NS_WARN_IF(!parent)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ JS::Rooted<JS::Value> body(cx);
+ Read(parent, cx, &body, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ aRv = xpc->JSValToVariant(cx, body, getter_AddRefs(variant));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+ else {
+ RefPtr<nsVariant> wvariant = new nsVariant();
+
+ if (NS_FAILED(wvariant->SetAsAString(mStringBody))) {
+ MOZ_ASSERT(false, "This should never fail!");
+ }
+
+ variant = wvariant;
+ }
+
+ // Send() has been already called, reset the proxy.
+ if (mProxy->mWorkerPrivate) {
+ mProxy->Reset();
+ }
+
+ mProxy->mWorkerPrivate = mWorkerPrivate;
+
+ MOZ_ASSERT(!mProxy->mSyncLoopTarget);
+ mProxy->mSyncLoopTarget.swap(mSyncLoopTarget);
+
+ if (mHasUploadListeners) {
+ // Send() can be called more than once before failure,
+ // so don't attach the upload listeners more than once.
+ if (!mProxy->mUploadEventListenersAttached &&
+ !mProxy->AddRemoveEventListeners(true, true)) {
+ MOZ_ASSERT(false, "This should never fail!");
+ }
+ }
+
+ mProxy->mArrayBufferResponseWasTransferred = false;
+
+ mProxy->mInnerChannelId++;
+
+ aRv = mProxy->mXHR->Send(variant);
+
+ if (!aRv.Failed()) {
+ mProxy->mOutstandingSendCount++;
+
+ if (!mHasUploadListeners) {
+ // Send() can be called more than once before failure,
+ // so don't attach the upload listeners more than once.
+ if (!mProxy->mUploadEventListenersAttached &&
+ !mProxy->AddRemoveEventListeners(true, true)) {
+ MOZ_ASSERT(false, "This should never fail!");
+ }
+ }
+ }
+}
+
+XMLHttpRequestWorker::XMLHttpRequestWorker(WorkerPrivate* aWorkerPrivate)
+: mWorkerPrivate(aWorkerPrivate),
+ mResponseType(XMLHttpRequestResponseType::Text), mTimeout(0),
+ mRooted(false), mBackgroundRequest(false), mWithCredentials(false),
+ mCanceled(false), mMozAnon(false), mMozSystem(false)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ mozilla::HoldJSObjects(this);
+}
+
+XMLHttpRequestWorker::~XMLHttpRequestWorker()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ ReleaseProxy(XHRIsGoingAway);
+
+ MOZ_ASSERT(!mRooted);
+
+ mozilla::DropJSObjects(this);
+}
+
+NS_IMPL_ADDREF_INHERITED(XMLHttpRequestWorker, XMLHttpRequestEventTarget)
+NS_IMPL_RELEASE_INHERITED(XMLHttpRequestWorker, XMLHttpRequestEventTarget)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XMLHttpRequestWorker)
+NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestWorker)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestWorker,
+ XMLHttpRequestEventTarget)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestWorker,
+ XMLHttpRequestEventTarget)
+ tmp->ReleaseProxy(XHRIsGoingAway);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
+ tmp->mStateData.mResponse.setUndefined();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestWorker,
+ XMLHttpRequestEventTarget)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mStateData.mResponse)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+/* static */ already_AddRefed<XMLHttpRequest>
+XMLHttpRequestWorker::Construct(const GlobalObject& aGlobal,
+ const MozXMLHttpRequestParameters& aParams,
+ ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+ MOZ_ASSERT(workerPrivate);
+
+ Telemetry::Accumulate(Telemetry::XHR_IN_WORKER, 1);
+
+ RefPtr<XMLHttpRequestWorker> xhr = new XMLHttpRequestWorker(workerPrivate);
+
+ if (workerPrivate->XHRParamsAllowed()) {
+ if (aParams.mMozSystem)
+ xhr->mMozAnon = true;
+ else
+ xhr->mMozAnon = aParams.mMozAnon;
+ xhr->mMozSystem = aParams.mMozSystem;
+ }
+
+ return xhr.forget();
+}
+
+void
+XMLHttpRequestWorker::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).
+ RefPtr<AsyncTeardownRunnable> runnable =
+ new AsyncTeardownRunnable(mProxy);
+ mProxy = nullptr;
+
+ if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) {
+ 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.
+ RefPtr<SyncTeardownRunnable> runnable =
+ new SyncTeardownRunnable(mWorkerPrivate, mProxy);
+ mProxy = nullptr;
+
+ ErrorResult forAssertionsOnly;
+ runnable->Dispatch(forAssertionsOnly);
+ if (forAssertionsOnly.Failed()) {
+ NS_ERROR("Failed to dispatch teardown runnable!");
+ }
+ }
+ }
+}
+
+void
+XMLHttpRequestWorker::MaybePin(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mRooted) {
+ return;
+ }
+
+ if (!HoldWorker(mWorkerPrivate, Canceling)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ NS_ADDREF_THIS();
+
+ mRooted = true;
+}
+
+void
+XMLHttpRequestWorker::MaybeDispatchPrematureAbortEvents(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mProxy);
+
+ // Only send readystatechange event when state changed.
+ bool isStateChanged = false;
+ if (mStateData.mReadyState != 4) {
+ isStateChanged = true;
+ mStateData.mReadyState = 4;
+ }
+
+ if (mProxy->mSeenUploadLoadStart) {
+ MOZ_ASSERT(mUpload);
+
+ DispatchPrematureAbortEvent(mUpload, NS_LITERAL_STRING("abort"), true,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ DispatchPrematureAbortEvent(mUpload, NS_LITERAL_STRING("loadend"), true,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ mProxy->mSeenUploadLoadStart = false;
+ }
+
+ if (mProxy->mSeenLoadStart) {
+ if (isStateChanged) {
+ DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("readystatechange"),
+ false, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("abort"), false, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("loadend"), false,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ mProxy->mSeenLoadStart = false;
+ }
+}
+
+void
+XMLHttpRequestWorker::DispatchPrematureAbortEvent(EventTarget* aTarget,
+ const nsAString& aEventType,
+ bool aUploadTarget,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(aTarget);
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<Event> event;
+ if (aEventType.EqualsLiteral("readystatechange")) {
+ event = NS_NewDOMEvent(aTarget, nullptr, nullptr);
+ event->InitEvent(aEventType, false, false);
+ }
+ else {
+ ProgressEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ if (aUploadTarget) {
+ init.mLengthComputable = mProxy->mLastUploadLengthComputable;
+ init.mLoaded = mProxy->mLastUploadLoaded;
+ init.mTotal = mProxy->mLastUploadTotal;
+ }
+ else {
+ init.mLengthComputable = mProxy->mLastLengthComputable;
+ init.mLoaded = mProxy->mLastLoaded;
+ init.mTotal = mProxy->mLastTotal;
+ }
+ event = ProgressEvent::Constructor(aTarget, aEventType, init);
+ }
+
+ if (!event) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ event->SetTrusted(true);
+
+ aTarget->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+}
+
+void
+XMLHttpRequestWorker::Unpin()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(mRooted, "Mismatched calls to Unpin!");
+
+ ReleaseWorker();
+
+ mRooted = false;
+
+ NS_RELEASE_THIS();
+}
+
+void
+XMLHttpRequestWorker::SendInternal(SendRunnable* aRunnable,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aRunnable);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ // No send() calls when open is running.
+ if (mProxy->mOpenCount) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ bool hasUploadListeners = mUpload ? mUpload->HasListeners() : false;
+
+ MaybePin(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ AutoUnpinXHR autoUnpin(this);
+ Maybe<AutoSyncLoopHolder> autoSyncLoop;
+
+ nsCOMPtr<nsIEventTarget> syncLoopTarget;
+ bool isSyncXHR = mProxy->mIsSyncXHR;
+ if (isSyncXHR) {
+ autoSyncLoop.emplace(mWorkerPrivate);
+ syncLoopTarget = autoSyncLoop->EventTarget();
+ }
+
+ mProxy->mOuterChannelId++;
+
+ aRunnable->SetSyncLoopTarget(syncLoopTarget);
+ aRunnable->SetHaveUploadListeners(hasUploadListeners);
+
+ aRunnable->Dispatch(aRv);
+ if (aRv.Failed()) {
+ // Dispatch() may have spun the event loop and we may have already unrooted.
+ // If so we don't want autoUnpin to try again.
+ if (!mRooted) {
+ autoUnpin.Clear();
+ }
+ return;
+ }
+
+ if (!isSyncXHR) {
+ autoUnpin.Clear();
+ MOZ_ASSERT(!autoSyncLoop);
+ return;
+ }
+
+ autoUnpin.Clear();
+
+ // Don't clobber an existing exception that we may have thrown on aRv
+ // already... though can there really be one? In any case, it seems to me
+ // that this autoSyncLoop->Run() can never fail, since the StopSyncLoop call
+ // for it will come from ProxyCompleteRunnable and that always passes true for
+ // the second arg.
+ if (!autoSyncLoop->Run() && !aRv.Failed()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+bool
+XMLHttpRequestWorker::Notify(Status aStatus)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (aStatus >= Canceling && !mCanceled) {
+ mCanceled = true;
+ ReleaseProxy(WorkerIsGoingAway);
+ }
+
+ return true;
+}
+
+void
+XMLHttpRequestWorker::Open(const nsACString& aMethod,
+ const nsAString& aUrl, bool aAsync,
+ const Optional<nsAString>& aUser,
+ const Optional<nsAString>& aPassword,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (mProxy) {
+ MaybeDispatchPrematureAbortEvents(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ else {
+ mProxy = new Proxy(this, mMozAnon, mMozSystem);
+ }
+
+ mProxy->mOuterEventStreamId++;
+
+ RefPtr<OpenRunnable> runnable =
+ new OpenRunnable(mWorkerPrivate, mProxy, aMethod, aUrl, aUser, aPassword,
+ mBackgroundRequest, mWithCredentials,
+ mTimeout, mResponseType);
+
+ ++mProxy->mOpenCount;
+ runnable->Dispatch(aRv);
+ if (aRv.Failed()) {
+ if (mProxy && !--mProxy->mOpenCount) {
+ ReleaseProxy();
+ }
+
+ return;
+ }
+
+ // We have been released in one of the nested Open() calls.
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ --mProxy->mOpenCount;
+ mProxy->mIsSyncXHR = !aAsync;
+}
+
+void
+XMLHttpRequestWorker::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<SetRequestHeaderRunnable> runnable =
+ new SetRequestHeaderRunnable(mWorkerPrivate, mProxy, aHeader, aValue);
+ runnable->Dispatch(aRv);
+}
+
+void
+XMLHttpRequestWorker::SetTimeout(uint32_t aTimeout, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ mTimeout = aTimeout;
+
+ if (!mProxy) {
+ // Open may not have been called yet, in which case we'll handle the
+ // timeout in OpenRunnable.
+ return;
+ }
+
+ RefPtr<SetTimeoutRunnable> runnable =
+ new SetTimeoutRunnable(mWorkerPrivate, mProxy, aTimeout);
+ runnable->Dispatch(aRv);
+}
+
+void
+XMLHttpRequestWorker::SetWithCredentials(bool aWithCredentials, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ mWithCredentials = aWithCredentials;
+
+ if (!mProxy) {
+ // Open may not have been called yet, in which case we'll handle the
+ // credentials in OpenRunnable.
+ return;
+ }
+
+ RefPtr<SetWithCredentialsRunnable> runnable =
+ new SetWithCredentialsRunnable(mWorkerPrivate, mProxy, aWithCredentials);
+ runnable->Dispatch(aRv);
+}
+
+void
+XMLHttpRequestWorker::SetMozBackgroundRequest(bool aBackgroundRequest,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ mBackgroundRequest = aBackgroundRequest;
+
+ if (!mProxy) {
+ // Open may not have been called yet, in which case we'll handle the
+ // background request in OpenRunnable.
+ return;
+ }
+
+ RefPtr<SetBackgroundRequestRunnable> runnable =
+ new SetBackgroundRequestRunnable(mWorkerPrivate, mProxy,
+ aBackgroundRequest);
+ runnable->Dispatch(aRv);
+}
+
+XMLHttpRequestUpload*
+XMLHttpRequestWorker::GetUpload(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return nullptr;
+ }
+
+ if (!mUpload) {
+ mUpload = new XMLHttpRequestUpload();
+
+ if (!mUpload) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ return mUpload;
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, NullString());
+
+ // Nothing to clone.
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, const nsAString& aBody,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, aBody);
+
+ // Nothing to clone.
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, JS::Handle<JSObject*> aBody,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aBody);
+
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ JS::Rooted<JS::Value> valToClone(aCx);
+ if (JS_IsArrayBufferObject(aBody) || JS_IsArrayBufferViewObject(aBody)) {
+ valToClone.setObject(*aBody);
+ }
+ else {
+ JS::Rooted<JS::Value> obj(aCx, JS::ObjectValue(*aBody));
+ JSString* bodyStr = JS::ToString(aCx, obj);
+ if (!bodyStr) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ valToClone.setString(bodyStr);
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
+
+ sendRunnable->Write(aCx, valToClone, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, Blob& aBody, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, &aBody, &value)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<BlobImpl> blobImpl = aBody.Impl();
+ MOZ_ASSERT(blobImpl);
+
+ aRv = blobImpl->SetMutable(false);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
+
+ sendRunnable->Write(aCx, value, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, FormData& aBody, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, &aBody, &value)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
+
+ sendRunnable->Write(aCx, value, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, URLSearchParams& aBody,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, &aBody, &value)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
+
+ sendRunnable->Write(aCx, value, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, const ArrayBuffer& aBody,
+ ErrorResult& aRv)
+{
+ JS::Rooted<JSObject*> obj(mWorkerPrivate->GetJSContext(), aBody.Obj());
+ return Send(aCx, obj, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, const ArrayBufferView& aBody,
+ ErrorResult& aRv)
+{
+ if (JS_IsTypedArrayObject(aBody.Obj()) &&
+ JS_GetTypedArraySharedness(aBody.Obj())) {
+ // Throw if the object is mapping shared memory (must opt in).
+ aRv.ThrowTypeError<MSG_TYPEDARRAY_IS_SHARED>(NS_LITERAL_STRING("Argument of XMLHttpRequest.send"));
+ return;
+ }
+ JS::Rooted<JSObject*> obj(aCx, aBody.Obj());
+ return Send(aCx, obj, aRv);
+}
+
+void
+XMLHttpRequestWorker::Abort(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ return;
+ }
+
+ MaybeDispatchPrematureAbortEvents(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (mStateData.mReadyState == 4) {
+ // No one did anything to us while we fired abort events, so reset our state
+ // to "unsent"
+ mStateData.mReadyState = 0;
+ }
+
+ mProxy->mOuterEventStreamId++;
+
+ RefPtr<AbortRunnable> runnable = new AbortRunnable(mWorkerPrivate, mProxy);
+ runnable->Dispatch(aRv);
+}
+
+void
+XMLHttpRequestWorker::GetResponseHeader(const nsACString& aHeader,
+ nsACString& aResponseHeader, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsCString responseHeader;
+ RefPtr<GetResponseHeaderRunnable> runnable =
+ new GetResponseHeaderRunnable(mWorkerPrivate, mProxy, aHeader,
+ responseHeader);
+ runnable->Dispatch(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ aResponseHeader = responseHeader;
+}
+
+void
+XMLHttpRequestWorker::GetAllResponseHeaders(nsACString& aResponseHeaders,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsCString responseHeaders;
+ RefPtr<GetAllResponseHeadersRunnable> runnable =
+ new GetAllResponseHeadersRunnable(mWorkerPrivate, mProxy, responseHeaders);
+ runnable->Dispatch(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ aResponseHeaders = responseHeaders;
+}
+
+void
+XMLHttpRequestWorker::OverrideMimeType(const nsAString& aMimeType, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ // We're supposed to throw if the state is not OPENED or HEADERS_RECEIVED. We
+ // can detect OPENED really easily but we can't detect HEADERS_RECEIVED in a
+ // non-racy way until the XHR state machine actually runs on this thread
+ // (bug 671047). For now we're going to let this work only if the Send()
+ // method has not been called, unless the send has been aborted.
+ if (!mProxy || (SendInProgress() &&
+ (mProxy->mSeenLoadStart ||
+ mStateData.mReadyState > nsIXMLHttpRequest::OPENED))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<OverrideMimeTypeRunnable> runnable =
+ new OverrideMimeTypeRunnable(mWorkerPrivate, mProxy, aMimeType);
+ runnable->Dispatch(aRv);
+}
+
+void
+XMLHttpRequestWorker::SetResponseType(XMLHttpRequestResponseType aResponseType,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ // "document" is fine for the main thread but not for a worker. Short-circuit
+ // that here.
+ if (aResponseType == XMLHttpRequestResponseType::Document) {
+ return;
+ }
+
+ if (!mProxy) {
+ // Open() has not been called yet. We store the responseType and we will use
+ // it later in Open().
+ mResponseType = aResponseType;
+ return;
+ }
+
+ if (SendInProgress() &&
+ (mProxy->mSeenLoadStart ||
+ mStateData.mReadyState > nsIXMLHttpRequest::OPENED)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<SetResponseTypeRunnable> runnable =
+ new SetResponseTypeRunnable(mWorkerPrivate, mProxy, aResponseType);
+ runnable->Dispatch(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ mResponseType = runnable->ResponseType();
+}
+
+void
+XMLHttpRequestWorker::GetResponse(JSContext* /* unused */,
+ JS::MutableHandle<JS::Value> aResponse,
+ ErrorResult& aRv)
+{
+ if (NS_SUCCEEDED(mStateData.mResponseTextResult) &&
+ mStateData.mResponse.isUndefined()) {
+ MOZ_ASSERT(NS_SUCCEEDED(mStateData.mResponseResult));
+
+ if (mStateData.mResponseText.IsEmpty()) {
+ mStateData.mResponse =
+ JS_GetEmptyStringValue(mWorkerPrivate->GetJSContext());
+ } else {
+ XMLHttpRequestStringSnapshotReaderHelper helper(mStateData.mResponseText);
+
+ JSString* str =
+ JS_NewUCStringCopyN(mWorkerPrivate->GetJSContext(),
+ helper.Buffer(), helper.Length());
+
+ if (!str) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ mStateData.mResponse.setString(str);
+ }
+ }
+
+ aRv = mStateData.mResponseResult;
+ aResponse.set(mStateData.mResponse);
+}
+
+void
+XMLHttpRequestWorker::GetResponseText(DOMString& aResponseText, ErrorResult& aRv)
+{
+ aRv = mStateData.mResponseTextResult;
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (!mStateData.mResponseText.GetAsString(aResponseText)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+}
+
+void
+XMLHttpRequestWorker::UpdateState(const StateData& aStateData,
+ bool aUseCachedArrayBufferResponse)
+{
+ if (aUseCachedArrayBufferResponse) {
+ MOZ_ASSERT(mStateData.mResponse.isObject() &&
+ JS_IsArrayBufferObject(&mStateData.mResponse.toObject()));
+
+ JS::Rooted<JS::Value> response(mWorkerPrivate->GetJSContext(),
+ mStateData.mResponse);
+ mStateData = aStateData;
+ mStateData.mResponse = response;
+ }
+ else {
+ mStateData = aStateData;
+ }
+
+ XMLHttpRequestBinding::ClearCachedResponseTextValue(this);
+}
+
+} // dom namespace
+} // mozilla namespace