/* -*- 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 "nsIDOMEventListener.h" #include "GeckoProfiler.h" #include "jsfriendapi.h" #include "js/ArrayBuffer.h" // JS::Is{,Detached}ArrayBufferObject #include "js/GCPolicyAPI.h" #include "js/JSON.h" #include "js/RootingAPI.h" // JS::{Handle,Heap,PersistentRooted} #include "js/TracingAPI.h" #include "js/Value.h" // JS::{Undefined,}Value #include "mozilla/ArrayUtils.h" #include "mozilla/HoldDropJSObjects.h" #include "mozilla/dom/Exceptions.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FormData.h" #include "mozilla/dom/ProgressEvent.h" #include "mozilla/dom/SerializedStackHolder.h" #include "mozilla/dom/StreamBlobImpl.h" #include "mozilla/dom/StructuredCloneHolder.h" #include "mozilla/dom/URLSearchParams.h" #include "mozilla/dom/WorkerScope.h" #include "mozilla/dom/WorkerRef.h" #include "mozilla/dom/WorkerRunnable.h" #include "mozilla/dom/XMLHttpRequestBinding.h" #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsJSUtils.h" #include "nsThreadUtils.h" #include "XMLHttpRequestMainThread.h" #include "XMLHttpRequestUpload.h" #include "mozilla/UniquePtr.h" extern mozilla::LazyLogModule gXMLHttpRequestLog; namespace mozilla::dom { using EventType = XMLHttpRequest::EventType; using Events = XMLHttpRequest::Events; /** * 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. RefPtr mWorkerRef; const ClientInfo mClientInfo; const Maybe mController; // Only ever dereferenced and/or checked on the worker thread. Cleared // explicitly on the worker thread inside XMLHttpRequestWorker::ReleaseProxy. WeakPtr mXMLHttpRequestPrivate; // XHR Params: bool mMozAnon; bool mMozSystem; // Only touched on the main thread. RefPtr mXHR; RefPtr mXHRUpload; nsCOMPtr mSyncLoopTarget; nsCOMPtr mSyncEventResponseTarget; uint32_t mInnerEventStreamId; uint32_t mInnerChannelId; uint32_t mOutstandingSendCount; // Only touched on the worker thread. uint32_t mOuterChannelId; uint32_t mOpenCount; uint64_t mLastLoaded; uint64_t mLastTotal; uint64_t mLastUploadLoaded; uint64_t mLastUploadTotal; nsresult mLastErrorDetailAtLoadend; bool mIsSyncXHR; bool mLastLengthComputable; bool mLastUploadLengthComputable; bool mSeenUploadLoadStart; bool mSeenUploadLoadEnd; // Only touched on the main thread. bool mUploadEventListenersAttached; bool mMainThreadSeenLoadStart; bool mInOpen; public: Proxy(XMLHttpRequestWorker* aXHRPrivate, const ClientInfo& aClientInfo, const Maybe& aController, bool aMozAnon, bool aMozSystem) : mClientInfo(aClientInfo), mController(aController), mXMLHttpRequestPrivate(aXHRPrivate), mMozAnon(aMozAnon), mMozSystem(aMozSystem), mInnerEventStreamId(aXHRPrivate->EventStreamId()), mInnerChannelId(0), mOutstandingSendCount(0), mOuterChannelId(0), mOpenCount(0), mLastLoaded(0), mLastTotal(0), mLastUploadLoaded(0), mLastUploadTotal(0), mLastErrorDetailAtLoadend(NS_OK), mIsSyncXHR(false), mLastLengthComputable(false), mLastUploadLengthComputable(false), mSeenUploadLoadStart(false), mSeenUploadLoadEnd(false), mUploadEventListenersAttached(false), mMainThreadSeenLoadStart(false), mInOpen(false) {} NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIDOMEVENTLISTENER // This method is called in OpenRunnable::MainThreadRunInternal(). The // OpenRunnable has to provide a valid WorkerPrivate for the Proxy's // initialization since OpenRunnable is a WorkerMainThreadRunnable, which // holds a ThreadSafeWorkerRef and blocks Worker's shutdown until the // execution returns back to the worker thread. bool Init(WorkerPrivate* aWorkerPrivate); void Teardown(); bool AddRemoveEventListeners(bool aUpload, bool aAdd); void Reset() { AssertIsOnMainThread(); if (mUploadEventListenersAttached) { AddRemoveEventListeners(true, false); } } already_AddRefed GetEventTarget() { AssertIsOnMainThread(); nsCOMPtr target = mSyncEventResponseTarget ? mSyncEventResponseTarget : mSyncLoopTarget; return target.forget(); } WorkerPrivate* Private() const { if (mWorkerRef) { return mWorkerRef->Private(); } return nullptr; } #ifdef DEBUG void DebugStoreWorkerRef(RefPtr& aWorkerRef) { MOZ_ASSERT(!NS_IsMainThread()); MutexAutoLock lock(mXHR->mTSWorkerRefMutex); mXHR->mTSWorkerRef = aWorkerRef; } void DebugForgetWorkerRef() { MOZ_ASSERT(!NS_IsMainThread()); MutexAutoLock lock(mXHR->mTSWorkerRefMutex); mXHR->mTSWorkerRef = nullptr; } #endif private: ~Proxy() { MOZ_ASSERT(!mXHR); MOZ_ASSERT(!mXHRUpload); MOZ_ASSERT(!mOutstandingSendCount); } }; class WorkerThreadProxySyncRunnable : public WorkerMainThreadRunnable { protected: RefPtr mProxy; private: // mErrorCode is set on the main thread by MainThreadRun and it's used at the // end of the Dispatch() to return the error code. nsresult mErrorCode; public: WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy) : WorkerMainThreadRunnable(aWorkerPrivate, "XHR"_ns), mProxy(aProxy), mErrorCode(NS_OK) { MOZ_ASSERT(aWorkerPrivate); MOZ_ASSERT(aProxy); aWorkerPrivate->AssertIsOnWorkerThread(); } void Dispatch(WorkerPrivate* aWorkerPrivate, WorkerStatus aFailStatus, ErrorResult& aRv) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); WorkerMainThreadRunnable::Dispatch(aWorkerPrivate, aFailStatus, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } if (NS_FAILED(mErrorCode)) { aRv.Throw(mErrorCode); } } protected: virtual ~WorkerThreadProxySyncRunnable() = default; virtual void RunOnMainThread(ErrorResult& aRv) = 0; private: virtual bool MainThreadRun() override; }; class SendRunnable final : public WorkerThreadProxySyncRunnable { RefPtr mBlobImpl; // WorkerMainThreadRunnable has a member mSyncLoopTarget to perform the // synchronous dispatch. The mSyncLoopTarget will be released after // WorkerMainThreadRunnable::Dispatch(). // However, to perform sync XHR, an additional sync loop is needed to wait // for the sync XHR response. This is because XMLHttpRequestMainThread // performs xhr in async way, and it causes the response to not be // available before SendRunnable returns back to the worker thread. // This is the event target to the additional sync loop. nsCOMPtr mSyncXHRSyncLoopTarget; bool mHasUploadListeners; public: SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, BlobImpl* aBlobImpl) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mBlobImpl(aBlobImpl), mHasUploadListeners(false) {} void SetHaveUploadListeners(bool aHasUploadListeners) { mHasUploadListeners = aHasUploadListeners; } void SetSyncXHRSyncLoopTarget(nsIEventTarget* aSyncXHRSyncLoopTarget) { mSyncXHRSyncLoopTarget = aSyncXHRSyncLoopTarget; } private: ~SendRunnable() = default; virtual void RunOnMainThread(ErrorResult& aRv) override; }; namespace { class MainThreadProxyRunnable : public MainThreadWorkerSyncRunnable { protected: RefPtr mProxy; MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, const char* aName = "MainThreadProxyRunnable") : MainThreadWorkerSyncRunnable(aProxy->GetEventTarget(), aName), mProxy(aProxy) { MOZ_ASSERT(aProxy); } virtual ~MainThreadProxyRunnable() = default; }; class AsyncTeardownRunnable final : public Runnable { RefPtr mProxy; public: explicit AsyncTeardownRunnable(Proxy* aProxy) : Runnable("dom::AsyncTeardownRunnable"), mProxy(aProxy) { MOZ_ASSERT(aProxy); } private: ~AsyncTeardownRunnable() = default; NS_IMETHOD Run() override { AssertIsOnMainThread(); mProxy->Teardown(); mProxy = nullptr; return NS_OK; } }; class LoadStartDetectionRunnable final : public Runnable, public nsIDOMEventListener { RefPtr mProxy; RefPtr mXHR; uint32_t mChannelId; bool mReceivedLoadStart; class ProxyCompleteRunnable final : public MainThreadProxyRunnable { uint32_t mChannelId; public: ProxyCompleteRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, uint32_t aChannelId) : MainThreadProxyRunnable(aWorkerPrivate, aProxy, "ProxyCompleteRunnable"), mChannelId(aChannelId) {} private: ~ProxyCompleteRunnable() = default; 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, NS_OK); } XMLHttpRequestWorker* xhrw = mProxy->mXMLHttpRequestPrivate.get(); if (xhrw && xhrw->SendInProgress()) { xhrw->Unpin(); } return true; } nsresult Cancel() override { return Run(); } }; public: explicit LoadStartDetectionRunnable(Proxy* aProxy) : Runnable("dom::LoadStartDetectionRunnable"), mProxy(aProxy), mXHR(aProxy->mXHR), mChannelId(mProxy->mInnerChannelId), mReceivedLoadStart(false) { AssertIsOnMainThread(); } NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIRUNNABLE NS_DECL_NSIDOMEVENTLISTENER bool RegisterAndDispatch() { AssertIsOnMainThread(); if (NS_FAILED( mXHR->AddEventListener(Events::loadstart, this, false, false))) { NS_WARNING("Failed to add event listener!"); return false; } MOZ_ASSERT_DEBUG_OR_FUZZING(mProxy && mProxy->Private()); return NS_SUCCEEDED(mProxy->Private()->DispatchToMainThread(this)); } private: ~LoadStartDetectionRunnable() { AssertIsOnMainThread(); } }; class EventRunnable final : public MainThreadProxyRunnable { const EventType& mType; UniquePtr mResponseData; nsCString mResponseURL; nsCString mStatusText; uint64_t mLoaded; uint64_t mTotal; uint32_t mEventStreamId; uint32_t mStatus; uint16_t mReadyState; bool mUploadEvent; bool mProgressEvent; bool mLengthComputable; nsresult mStatusResult; nsresult mErrorDetail; // 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 mScopeObj; public: EventRunnable(Proxy* aProxy, bool aUploadEvent, const EventType& aType, bool aLengthComputable, uint64_t aLoaded, uint64_t aTotal, JS::Handle aScopeObj) : MainThreadProxyRunnable(aProxy->Private(), aProxy, "EventRunnable"), mType(aType), mResponseData(new XMLHttpRequestWorker::ResponseData()), mLoaded(aLoaded), mTotal(aTotal), mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0), mUploadEvent(aUploadEvent), mProgressEvent(true), mLengthComputable(aLengthComputable), mStatusResult(NS_OK), mErrorDetail(NS_OK), mScopeObj(RootingCx(), aScopeObj) {} EventRunnable(Proxy* aProxy, bool aUploadEvent, const EventType& aType, JS::Handle aScopeObj) : MainThreadProxyRunnable(aProxy->Private(), aProxy, "EventRunnable"), mType(aType), mResponseData(new XMLHttpRequestWorker::ResponseData()), mLoaded(0), mTotal(0), mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0), mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0), mStatusResult(NS_OK), mErrorDetail(NS_OK), mScopeObj(RootingCx(), aScopeObj) {} private: ~EventRunnable() = default; bool PreDispatch(WorkerPrivate* /* unused */) final; bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override; }; class SyncTeardownRunnable final : public WorkerThreadProxySyncRunnable { public: SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy) {} private: ~SyncTeardownRunnable() = default; virtual void RunOnMainThread(ErrorResult& aRv) override { mProxy->Teardown(); 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() = default; virtual void RunOnMainThread(ErrorResult& aRv) override { // XXXedgar, do we intend to ignore the errors? 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() = default; 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() = default; virtual void RunOnMainThread(ErrorResult& aRv) override { mProxy->mXHR->SetResponseTypeRaw(mResponseType); 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() = default; 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() = default; 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() = default; 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() = default; virtual void RunOnMainThread(ErrorResult& aRv) override { mProxy->mXHR->GetResponseHeader(mHeader, mValue, aRv); } }; class OpenRunnable final : public WorkerThreadProxySyncRunnable { nsCString mMethod; nsCString mURL; Optional mUser; nsCString mUserStr; Optional mPassword; nsCString mPasswordStr; bool mBackgroundRequest; bool mWithCredentials; uint32_t mTimeout; XMLHttpRequestResponseType mResponseType; const nsString mMimeTypeOverride; // Remember the worker thread's stack when the XHR was opened, so that it can // be passed on to the net monitor. UniquePtr mOriginStack; // Remember the worker thread's stack when the XHR was opened for profiling // purposes. UniquePtr mSource; public: OpenRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, const nsACString& aMethod, const nsACString& aURL, const Optional& aUser, const Optional& aPassword, bool aBackgroundRequest, bool aWithCredentials, uint32_t aTimeout, XMLHttpRequestResponseType aResponseType, const nsString& aMimeTypeOverride, UniquePtr aOriginStack, UniquePtr aSource = nullptr) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMethod(aMethod), mURL(aURL), mBackgroundRequest(aBackgroundRequest), mWithCredentials(aWithCredentials), mTimeout(aTimeout), mResponseType(aResponseType), mMimeTypeOverride(aMimeTypeOverride), mOriginStack(std::move(aOriginStack)), mSource(std::move(aSource)) { if (aUser.WasPassed()) { mUserStr = aUser.Value(); mUser = &mUserStr; } if (aPassword.WasPassed()) { mPasswordStr = aPassword.Value(); mPassword = &mPasswordStr; } } private: ~OpenRunnable() = default; virtual void RunOnMainThread(ErrorResult& aRv) override { MOZ_ASSERT_IF(mProxy->mWorkerRef, mProxy->mWorkerRef->Private() == mWorkerRef->Private()); // mProxy wants a valid ThreadSafeWorkerRef for the duration of our call, // but mProxy->mWorkerRef may be null if a send is not currently active, // so save the existing value for the duration of the call. RefPtr oldWorker = std::move(mProxy->mWorkerRef); // WorkerMainThreadRunnable::mWorkerRef must not be nullptr here, since // when get here, it means this WorkerMainThreadRunnable had already be // dispatched successfully and the execution is on the main thread. MOZ_ASSERT_DEBUG_OR_FUZZING(mWorkerRef); // Set mProxy->mWorkerRef as OpenRunnable::mWorkerRef which is from // WorkerMainThreadRunnable during the runnable execution. // Let OpenRunnable keep a reference for dispatching // MainThreadStopSyncRunnable back to the Worker thread after the main // thread execution completes. mProxy->mWorkerRef = mWorkerRef; MainThreadRunInternal(aRv); // Restore the previous activated WorkerRef for send. mProxy->mWorkerRef = std::move(oldWorker); } void MainThreadRunInternal(ErrorResult& aRv); }; 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() = default; 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() = default; 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(WorkerPrivate* aWorkerPrivate) { AssertIsOnMainThread(); MOZ_ASSERT(aWorkerPrivate); if (mXHR) { return true; } nsPIDOMWindowInner* ownerWindow = aWorkerPrivate->GetWindow(); if (ownerWindow && !ownerWindow->IsCurrentInnerWindow()) { NS_WARNING("Window has navigated, cannot create XHR here."); return false; } mXHR = new XMLHttpRequestMainThread(ownerWindow ? ownerWindow->AsGlobal() : nullptr); mXHR->Construct(aWorkerPrivate->GetPrincipal(), aWorkerPrivate->CookieJarSettings(), true, aWorkerPrivate->GetBaseURI(), aWorkerPrivate->GetLoadGroup(), aWorkerPrivate->GetPerformanceStorage(), aWorkerPrivate->CSPEventListener()); mXHR->SetParameters(mMozAnon, mMozSystem); mXHR->SetClientInfoAndController(mClientInfo, mController); 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() { 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 (mSyncLoopTarget) { // We have an unclosed sync loop. Fix that now. RefPtr runnable = new MainThreadStopSyncLoopRunnable(std::move(mSyncLoopTarget), NS_ERROR_FAILURE); MOZ_ALWAYS_TRUE(runnable->Dispatch(mWorkerRef->Private())); } mOutstandingSendCount = 0; } mWorkerRef = nullptr; mXHRUpload = nullptr; mXHR = nullptr; } MOZ_ASSERT(!mWorkerRef); MOZ_ASSERT(!mSyncLoopTarget); // If there are rare edge cases left that violate our invariants // just ensure that they won't harm us too much. mWorkerRef = nullptr; mSyncLoopTarget = nullptr; } bool Proxy::AddRemoveEventListeners(bool aUpload, bool aAdd) { AssertIsOnMainThread(); NS_ASSERTION(!aUpload || (mUploadEventListenersAttached && !aAdd) || (!mUploadEventListenersAttached && aAdd), "Messed up logic for upload listeners!"); RefPtr targetHelper = aUpload ? static_cast(mXHRUpload.get()) : static_cast(mXHR.get()); MOZ_ASSERT(targetHelper, "This should never fail!"); for (const EventType* type : Events::All) { if (aUpload && *type == Events::readystatechange) { continue; } if (aAdd) { if (NS_FAILED(targetHelper->AddEventListener(*type, this, false))) { return false; } } else { targetHelper->RemoveEventListener(*type, this, false); } } if (aUpload) { mUploadEventListenersAttached = aAdd; } return true; } NS_IMPL_ISUPPORTS(Proxy, nsIDOMEventListener) NS_IMETHODIMP Proxy::HandleEvent(Event* aEvent) { AssertIsOnMainThread(); // EventRunnable::WorkerRun will bail out if mXMLHttpRequestWorker is null, // so we do not need to prevent the dispatch from the main thread such that // we do not need to touch it off-worker-thread. if (!mWorkerRef) { NS_ERROR("Shouldn't get here!"); return NS_OK; } nsAutoString _type; aEvent->GetType(_type); const EventType* typePtr = Events::Find(_type); MOZ_DIAGNOSTIC_ASSERT(typePtr, "Shouldn't get non-XMLHttpRequest events"); const EventType& type = *typePtr; bool isUploadTarget = mXHR != aEvent->GetTarget(); ProgressEvent* progressEvent = aEvent->AsProgressEvent(); if (mInOpen && type == Events::readystatechange) { if (mXHR->ReadyState() == 1) { mInnerEventStreamId++; } } { AutoJSAPI jsapi; JSObject* junkScope = xpc::UnprivilegedJunkScope(fallible); if (!junkScope || !jsapi.Init(junkScope)) { return NS_ERROR_FAILURE; } JSContext* cx = jsapi.cx(); JS::Rooted value(cx); if (!GetOrCreateDOMReflectorNoWrap(cx, mXHR, &value)) { return NS_ERROR_FAILURE; } JS::Rooted scope(cx, &value.toObject()); RefPtr runnable; if (progressEvent) { if (!mIsSyncXHR || type != Events::progress) { runnable = new EventRunnable( this, isUploadTarget, type, progressEvent->LengthComputable(), progressEvent->Loaded(), progressEvent->Total(), scope); } } else { runnable = new EventRunnable(this, isUploadTarget, type, scope); } if (runnable) { runnable->Dispatch(mWorkerRef->Private()); } } if (!isUploadTarget) { if (type == Events::loadstart) { mMainThreadSeenLoadStart = true; } else if (mMainThreadSeenLoadStart && type == Events::loadend) { mMainThreadSeenLoadStart = false; RefPtr runnable = new LoadStartDetectionRunnable(this); if (!runnable->RegisterAndDispatch()) { NS_WARNING("Failed to dispatch LoadStartDetectionRunnable!"); } } } return NS_OK; } NS_IMPL_ISUPPORTS_INHERITED(LoadStartDetectionRunnable, Runnable, nsIDOMEventListener) NS_IMETHODIMP LoadStartDetectionRunnable::Run() { AssertIsOnMainThread(); mXHR->RemoveEventListener(Events::loadstart, this, false); if (!mReceivedLoadStart) { if (mProxy->mOutstandingSendCount > 1) { mProxy->mOutstandingSendCount--; } else if (mProxy->mOutstandingSendCount == 1) { mProxy->Reset(); RefPtr runnable = new ProxyCompleteRunnable(mProxy->Private(), mProxy, mChannelId); if (runnable->Dispatch(mProxy->Private())) { mProxy->mWorkerRef = nullptr; mProxy->mSyncLoopTarget = nullptr; mProxy->mOutstandingSendCount--; } } } mProxy = nullptr; mXHR = nullptr; return NS_OK; } NS_IMETHODIMP LoadStartDetectionRunnable::HandleEvent(Event* aEvent) { AssertIsOnMainThread(); #ifdef DEBUG { nsAutoString type; aEvent->GetType(type); MOZ_ASSERT(type == Events::loadstart); } #endif mReceivedLoadStart = true; return NS_OK; } bool EventRunnable::PreDispatch(WorkerPrivate* /* unused */) { AssertIsOnMainThread(); AutoJSAPI jsapi; DebugOnly ok = jsapi.Init(xpc::NativeGlobal(mScopeObj)); MOZ_ASSERT(ok); JSContext* cx = jsapi.cx(); // Now keep the mScopeObj alive for the duration JS::Rooted scopeObj(cx, mScopeObj); // And reset mScopeObj now, before we have a chance to run its destructor on // some background thread. mScopeObj.reset(); RefPtr& xhr = mProxy->mXHR; MOZ_ASSERT(xhr); ErrorResult rv; XMLHttpRequestResponseType type = xhr->ResponseType(); // We want to take the result data only if this is available. if (mType == Events::readystatechange) { switch (type) { case XMLHttpRequestResponseType::_empty: case XMLHttpRequestResponseType::Text: { xhr->GetResponseText(mResponseData->mResponseText, rv); mResponseData->mResponseResult = rv.StealNSResult(); break; } case XMLHttpRequestResponseType::Blob: { mResponseData->mResponseBlobImpl = xhr->GetResponseBlobImpl(); break; } case XMLHttpRequestResponseType::Arraybuffer: { mResponseData->mResponseArrayBufferBuilder = xhr->GetResponseArrayBufferBuilder(); break; } case XMLHttpRequestResponseType::Json: { mResponseData->mResponseResult = xhr->GetResponseTextForJSON(mResponseData->mResponseJSON); break; } default: MOZ_ASSERT_UNREACHABLE("Invalid response type"); return false; } } mStatus = xhr->GetStatus(rv); mStatusResult = rv.StealNSResult(); mErrorDetail = xhr->ErrorDetail(); xhr->GetStatusText(mStatusText, rv); MOZ_ASSERT(!rv.Failed()); mReadyState = xhr->ReadyState(); xhr->GetResponseURL(mResponseURL); return true; } bool EventRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { if (!mProxy->mXMLHttpRequestPrivate) { // Object was finalized, bail. return true; } if (mEventStreamId != mProxy->mXMLHttpRequestPrivate->EventStreamId()) { // Threads raced, this event is now obsolete. return true; } if (mType == Events::loadend) { mProxy->mLastErrorDetailAtLoadend = mErrorDetail; } bool isLoadStart = mType == Events::loadstart; if (mUploadEvent) { if (isLoadStart) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("Saw upload.loadstart event on main thread")); mProxy->mSeenUploadLoadStart = true; } else if (mType == Events::loadend) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("Saw upload.loadend event on main thread")); mProxy->mSeenUploadLoadEnd = true; } } if (mProgressEvent) { // Cache these in case we need them for an error event. if (mUploadEvent) { mProxy->mLastUploadLengthComputable = mLengthComputable; mProxy->mLastUploadLoaded = mLoaded; mProxy->mLastUploadTotal = mTotal; } else { mProxy->mLastLengthComputable = mLengthComputable; mProxy->mLastLoaded = mLoaded; mProxy->mLastTotal = mTotal; } } UniquePtr state( new XMLHttpRequestWorker::StateData()); state->mStatusResult = mStatusResult; state->mStatus = mStatus; state->mStatusText = mStatusText; state->mReadyState = mReadyState; state->mResponseURL = mResponseURL; XMLHttpRequestWorker* xhr = mProxy->mXMLHttpRequestPrivate; xhr->UpdateState(std::move(state), mType == Events::readystatechange ? std::move(mResponseData) : nullptr); if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) { return true; } XMLHttpRequestEventTarget* target; if (mUploadEvent) { target = xhr->GetUploadObjectNoCreate(); } else { target = xhr; } MOZ_ASSERT(target); RefPtr 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) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("%p unable to fire %s event (%u,%u,%" PRIu64 ",%" PRIu64 ")", mProxy->mXHR.get(), mType.cStr, mUploadEvent, mLengthComputable, mLoaded, mTotal)); return false; } event->SetTrusted(true); MOZ_LOG( gXMLHttpRequestLog, LogLevel::Debug, ("%p firing %s event (%u,%u,%" PRIu64 ",%" PRIu64 ")", mProxy->mXHR.get(), mType.cStr, mUploadEvent, mLengthComputable, mLoaded, mTotal)); target->DispatchEvent(*event); return true; } bool WorkerThreadProxySyncRunnable::MainThreadRun() { AssertIsOnMainThread(); nsCOMPtr 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++; MOZ_ASSERT(mWorkerRef); MOZ_ASSERT_IF(mProxy->mWorkerRef, mProxy->mWorkerRef->Private() == mWorkerRef->Private()); // mProxy wants a valid ThreadSafeWorkerRef for the duration of our call, // but mProxy->mWorkerRef may be null if a send is not currently active, // so save the existing value for the duration of the call. RefPtr oldWorker = std::move(mProxy->mWorkerRef); // WorkerMainThreadRunnable::mWorkerRef must not be nullptr here, since // when get here, it means this WorkerMainThreadRunnable had already be // dispatched successfully and the execution is on the main thread. MOZ_ASSERT_DEBUG_OR_FUZZING(mWorkerRef); // Set mProxy->mWorkerRef as AbortRunnable::mWorkerRef which is from // WorkerMainThreadRunnable during the runnable execution. // Let AbortRunnable keep a reference for dispatching // MainThreadStopSyncRunnable back to the Worker thread after the main thread // execution completes. mProxy->mWorkerRef = mWorkerRef; mProxy->mXHR->Abort(aRv); // Restore the activated WorkerRef to mProxy for the previous Send(). mProxy->mWorkerRef = std::move(oldWorker); mProxy->Reset(); } void OpenRunnable::MainThreadRunInternal(ErrorResult& aRv) { MOZ_ASSERT(mWorkerRef); if (!mProxy->Init(mWorkerRef->Private())) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } if (mBackgroundRequest) { mProxy->mXHR->SetMozBackgroundRequestExternal(mBackgroundRequest, aRv); if (aRv.Failed()) { return; } } if (mOriginStack) { mProxy->mXHR->SetOriginStack(std::move(mOriginStack)); } if (mWithCredentials) { mProxy->mXHR->SetWithCredentials(mWithCredentials, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } } if (mTimeout) { mProxy->mXHR->SetTimeout(mTimeout, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } } if (!mMimeTypeOverride.IsVoid()) { mProxy->mXHR->OverrideMimeType(mMimeTypeOverride, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } } MOZ_ASSERT(!mProxy->mInOpen); mProxy->mInOpen = true; mProxy->mXHR->Open( mMethod, mURL, true, mUser.WasPassed() ? mUser.Value() : VoidCString(), mPassword.WasPassed() ? mPassword.Value() : VoidCString(), aRv); MOZ_ASSERT(mProxy->mInOpen); mProxy->mInOpen = false; if (NS_WARN_IF(aRv.Failed())) { return; } if (mSource) { mProxy->mXHR->SetSource(std::move(mSource)); } mProxy->mXHR->SetResponseType(mResponseType, aRv); } void SendRunnable::RunOnMainThread(ErrorResult& aRv) { // Before we change any state let's check if we can send. if (!mProxy->mXHR->CanSend(aRv)) { return; } Nullable< DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString> payload; if (!mBlobImpl) { payload.SetNull(); } else { JS::Rooted globalObject(RootingCx(), xpc::UnprivilegedJunkScope(fallible)); if (NS_WARN_IF(!globalObject)) { aRv.Throw(NS_ERROR_FAILURE); return; } nsCOMPtr parent = xpc::NativeGlobal(globalObject); if (NS_WARN_IF(!parent)) { aRv.Throw(NS_ERROR_FAILURE); return; } RefPtr blob = Blob::Create(parent, mBlobImpl); MOZ_ASSERT(blob); DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString& ref = payload.SetValue(); ref.SetAsBlob() = blob; } // Send() has been already called, reset the proxy. if (mProxy->mWorkerRef) { mProxy->Reset(); } MOZ_ASSERT(mWorkerRef); mProxy->mWorkerRef = mWorkerRef; MOZ_ASSERT(!mProxy->mSyncLoopTarget); mProxy->mSyncLoopTarget.swap(mSyncXHRSyncLoopTarget); 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->mInnerChannelId++; mProxy->mXHR->Send(payload, aRv); 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!"); } } } else { // In case of failure we just break the sync loop mProxy->mSyncLoopTarget = nullptr; mSyncXHRSyncLoopTarget = nullptr; } } XMLHttpRequestWorker::XMLHttpRequestWorker(WorkerPrivate* aWorkerPrivate, nsIGlobalObject* aGlobalObject) : XMLHttpRequest(aGlobalObject), mResponseType(XMLHttpRequestResponseType::_empty), mStateData(new StateData()), mResponseData(new ResponseData()), mResponseArrayBufferValue(nullptr), mResponseJSONValue(JS::UndefinedValue()), mTimeout(0), mBackgroundRequest(false), mWithCredentials(false), mCanceled(false), mFlagSendActive(false), mMozAnon(false), mMozSystem(false), mMimeTypeOverride(VoidString()) { aWorkerPrivate->AssertIsOnWorkerThread(); mozilla::HoldJSObjects(this); } XMLHttpRequestWorker::~XMLHttpRequestWorker() { ReleaseProxy(XHRIsGoingAway); MOZ_ASSERT(!mWorkerRef); mozilla::DropJSObjects(this); } NS_IMPL_ADDREF_INHERITED(XMLHttpRequestWorker, XMLHttpRequestEventTarget) NS_IMPL_RELEASE_INHERITED(XMLHttpRequestWorker, XMLHttpRequestEventTarget) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(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(mResponseBlob) 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->mResponseData = nullptr; tmp->mResponseBlob = nullptr; tmp->mResponseArrayBufferValue = nullptr; tmp->mResponseJSONValue.setUndefined(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestWorker, XMLHttpRequestEventTarget) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResponseArrayBufferValue) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResponseJSONValue) NS_IMPL_CYCLE_COLLECTION_TRACE_END /* static */ already_AddRefed XMLHttpRequestWorker::Construct( const GlobalObject& aGlobal, const MozXMLHttpRequestParameters& aParams, ErrorResult& aRv) { JSContext* cx = aGlobal.Context(); WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx); MOZ_ASSERT(workerPrivate); nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); if (NS_WARN_IF(!global)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } RefPtr xhr = new XMLHttpRequestWorker(workerPrivate, global); if (workerPrivate->XHRParamsAllowed()) { if (aParams.mMozSystem) { xhr->mMozAnon = true; } else { xhr->mMozAnon = aParams.mMozAnon.WasPassed() ? aParams.mMozAnon.Value() : false; } xhr->mMozSystem = aParams.mMozSystem; } return xhr.forget(); } void XMLHttpRequestWorker::ReleaseProxy(ReleaseType aType) { if (mProxy) { WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(workerPrivate); if (aType == XHRIsGoingAway) { // Coming here means the XHR was GC'd, so we can't be pinned. MOZ_ASSERT(!mProxy->mXMLHttpRequestPrivate || !mProxy->mXMLHttpRequestPrivate->mPinnedSelfRef); // We need to clear our weak pointer on the worker thread, let's do it now // before doing it implicitly in the Proxy dtor on the wrong thread. mProxy->mXMLHttpRequestPrivate = nullptr; // We're in a GC finalizer, so we can't do a sync call here (and we don't // need to). RefPtr runnable = new AsyncTeardownRunnable(mProxy); mProxy = nullptr; if (NS_FAILED(workerPrivate->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. mEventStreamId++; } // Ensure we are unpinned before we clear the weak reference. RefPtr self = this; if (mPinnedSelfRef) { Unpin(); } mProxy->mXMLHttpRequestPrivate = nullptr; // We need to make a sync call here. RefPtr runnable = new SyncTeardownRunnable(workerPrivate, mProxy); mProxy = nullptr; IgnoredErrorResult forAssertionsOnly; // This runnable _must_ be executed. // XXX This is a bit weird the failure status is Dead. Dispatching this // WorkerThreadRunnable in Killing status is not reasonable for Worker. runnable->Dispatch(workerPrivate, Dead, forAssertionsOnly); MOZ_DIAGNOSTIC_ASSERT(!forAssertionsOnly.Failed()); } } } void XMLHttpRequestWorker::MaybePin(ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); if (mWorkerRef) { return; } WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); RefPtr self = this; RefPtr workerRef = StrongWorkerRef::Create(workerPrivate, "XMLHttpRequestWorker", [self]() { if (!self->mCanceled) { self->mCanceled = true; self->ReleaseProxy(WorkerIsGoingAway); } }); if (NS_WARN_IF(!workerRef)) { aRv.Throw(NS_ERROR_FAILURE); return; } mWorkerRef = MakeRefPtr(workerRef); mPinnedSelfRef = this; #ifdef DEBUG mProxy->DebugStoreWorkerRef(mWorkerRef); #endif } void XMLHttpRequestWorker::SetResponseToNetworkError() { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("SetResponseToNetworkError")); mStateData->mStatus = 0; mStateData->mStatusText.Truncate(); if (mProxy) { mProxy->mLastLengthComputable = false; mProxy->mLastLoaded = 0; mProxy->mLastTotal = 0; mProxy->mLastUploadLengthComputable = false; mProxy->mLastUploadLoaded = 0; mProxy->mLastUploadTotal = 0; } } void XMLHttpRequestWorker::RequestErrorSteps( ErrorResult& aRv, const ErrorProgressEventType& aEventType, nsresult aException) { // https://xhr.spec.whatwg.org/#request-error-steps MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("RequestErrorSteps(%s)", aEventType.cStr)); MOZ_ASSERT(mProxy); // Step 1: Set xhr’s state to done. mStateData->mReadyState = XMLHttpRequest_Binding::DONE; // Step 2: Unset xhr’s send() flag. mFlagSend = false; // Step 3: Set xhr’s response to a network error. SetResponseToNetworkError(); // Step 4: If xhr’s synchronous flag is set, then throw exception. if (!mProxy || mProxy->mIsSyncXHR) { aRv.Throw(aException); return; } // Step 5: Fire an event named readystatechange at xhr. if (!FireEvent(this, Events::readystatechange, false, aRv)) { return; } // Step 6: If xhr’s upload complete flag is unset, then: if (mUpload && mProxy && mProxy->mSeenUploadLoadStart && !mProxy->mSeenUploadLoadEnd) { // Gecko-specific: we can only know whether the proxy XHR's upload // complete flag is set by waiting for the related upload loadend // event to happen (at which point upload complete has just been set, // either in Request Error Steps or processRequestEndOfBody. // Step 6.1: Set xhr’s upload complete flag. // We don't need to keep track of this. // Gecko-specific: we must Fire the loadstart event, // as we have not done so yet. if (!FireEvent(mUpload, Events::loadstart, true, aRv)) { return; } // Step 6.2: If xhr’s upload listener flag is set, then: // We know there must be listeners since we saw an upload loadstart. // Step 6.2.1: Fire a progress event named event at xhr’s upload object with // 0 and 0. if (!FireEvent(mUpload, aEventType, true, aRv)) { return; } // Step 6.2.2: Fire a progress event named loadend at xhr’s upload object // with 0 and 0. if (!FireEvent(mUpload, Events::loadend, true, aRv)) { return; } } // Step 7: Fire a progress event named event at xhr with 0 and 0. if (!FireEvent(this, aEventType, true, aRv)) { return; } // Step 8: Fire a progress event named loadend at xhr with 0 and 0. FireEvent(this, Events::loadend, true, aRv); } // A false return value here indicates that we should consider the XHR // to have been re-opened, or something catastrophic to have happened, // where we should stop running any code we normally would after firing // the event (such as firing more events). This includes if an exception // is thrown in aRv. bool XMLHttpRequestWorker::FireEvent(EventTarget* aTarget, const EventType& aEventType, bool aUploadTarget, ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); MOZ_ASSERT(aTarget); if (!mProxy) { aRv.Throw(NS_ERROR_FAILURE); return false; } uint32_t currentEventStreamId = mEventStreamId; RefPtr event; if (aEventType == Events::readystatechange) { event = NS_NewDOMEvent(aTarget, nullptr, nullptr); event->InitEvent(aEventType, false, false); } else { if (mProxy->mIsSyncXHR && aEventType == Events::progress) { return true; } 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 false; } event->SetTrusted(true); MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("%p firing %s pre-abort event (%u,%u,%" PRIu64 ",%" PRIu64, this, aEventType.cStr, aUploadTarget, aUploadTarget ? mProxy->mLastUploadLengthComputable : mProxy->mLastLengthComputable, aUploadTarget ? mProxy->mLastUploadLoaded : mProxy->mLastLoaded, aUploadTarget ? mProxy->mLastUploadTotal : mProxy->mLastTotal)); aTarget->DispatchEvent(*event); // if dispatching the event caused code to run which re-opened us, and // therefore changed our event stream, return false. return currentEventStreamId == mEventStreamId; } void XMLHttpRequestWorker::Unpin() { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); MOZ_ASSERT(mWorkerRef, "Mismatched calls to Unpin!"); #ifdef DEBUG if (mProxy) { // The proxy will be gone if WorkerIsGoingAway mProxy->DebugForgetWorkerRef(); } #endif mWorkerRef = nullptr; mPinnedSelfRef = nullptr; } uint16_t XMLHttpRequestWorker::ReadyState() const { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetReadyState(%u)", mStateData->mReadyState)); return mStateData->mReadyState; } void XMLHttpRequestWorker::SendInternal(const BodyExtractorBase* aBody, ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); // We don't really need to keep the same body-type when we proxy the send() // call to the main-thread XHR. Let's extract the nsIInputStream from the // aBody and let's wrap it into a StreamBlobImpl. RefPtr blobImpl; if (aBody) { nsAutoCString charset; nsAutoCString defaultContentType; nsCOMPtr uploadStream; uint64_t size_u64; aRv = aBody->GetAsStream(getter_AddRefs(uploadStream), &size_u64, defaultContentType, charset); if (NS_WARN_IF(aRv.Failed())) { return; } blobImpl = StreamBlobImpl::Create(uploadStream.forget(), NS_ConvertUTF8toUTF16(defaultContentType), size_u64, u"StreamBlobImpl"_ns); MOZ_ASSERT(blobImpl); } WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); RefPtr sendRunnable = new SendRunnable(workerPrivate, mProxy, blobImpl); // 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; } RefPtr selfRef = this; AutoUnpinXHR autoUnpin(this); Maybe syncXHRSyncLoop; nsCOMPtr syncXHRSyncLoopTarget; bool isSyncXHR = mProxy->mIsSyncXHR; if (isSyncXHR) { syncXHRSyncLoop.emplace(workerPrivate, Canceling); syncXHRSyncLoopTarget = syncXHRSyncLoop->GetSerialEventTarget(); if (!syncXHRSyncLoopTarget) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } } mProxy->mOuterChannelId++; sendRunnable->SetSyncXHRSyncLoopTarget(syncXHRSyncLoopTarget); sendRunnable->SetHaveUploadListeners(hasUploadListeners); mFlagSend = true; sendRunnable->Dispatch(workerPrivate, Canceling, 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 (!mWorkerRef) { autoUnpin.Clear(); } return; } if (!isSyncXHR) { autoUnpin.Clear(); MOZ_ASSERT(!syncXHRSyncLoop); return; } autoUnpin.Clear(); bool succeeded = NS_SUCCEEDED(syncXHRSyncLoop->Run()); // Throw appropriately If a sync XHR failed per spec's RequestErrorSteps if (isSyncXHR && mProxy) { nsresult error = mProxy->mLastErrorDetailAtLoadend; if (error == NS_ERROR_DOM_ABORT_ERR) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Info, ("%p throwing NS_ERROR_DOM_ABORT_ERR", this)); aRv.Throw(error); return; } if (error == NS_ERROR_DOM_TIMEOUT_ERR) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Info, ("%p throwing NS_ERROR_DOM_TIMEOUT_ERR", this)); aRv.Throw(error); return; } if (error == NS_ERROR_DOM_NETWORK_ERR || NS_ERROR_GET_MODULE(error) == NS_ERROR_MODULE_NETWORK) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Info, ("%p throwing NS_ERROR_DOM_NETWORK_ERR (0x%" PRIx32 ")", this, static_cast(error))); aRv.Throw(NS_ERROR_DOM_NETWORK_ERR); return; } } // 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 (!succeeded && !aRv.Failed()) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("%p SendInternal failed; throwing NS_ERROR_FAILURE", this)); aRv.Throw(NS_ERROR_FAILURE); } } void XMLHttpRequestWorker::Open(const nsACString& aMethod, const nsACString& aUrl, bool aAsync, const Optional& aUser, const Optional& aPassword, ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("%p Open(%s,%s,%d)", this, PromiseFlatCString(aMethod).get(), PromiseFlatCString(aUrl).get(), aAsync)); if (mCanceled) { aRv.ThrowUncatchableException(); return; } WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); mFlagSend = false; bool alsoOverrideMimeType = false; if (!mProxy) { Maybe clientInfo(workerPrivate->GlobalScope()->GetClientInfo()); if (clientInfo.isNothing()) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } mProxy = new Proxy(this, clientInfo.ref(), workerPrivate->GlobalScope()->GetController(), mMozAnon, mMozSystem); alsoOverrideMimeType = true; } mProxy->mSeenUploadLoadStart = false; mProxy->mSeenUploadLoadEnd = false; SetResponseToNetworkError(); mEventStreamId++; UniquePtr stack; if (workerPrivate->IsWatchedByDevTools()) { if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) { stack = GetCurrentStackForNetMonitor(cx); } } RefPtr runnable = new OpenRunnable( workerPrivate, mProxy, aMethod, aUrl, aUser, aPassword, mBackgroundRequest, mWithCredentials, mTimeout, mResponseType, alsoOverrideMimeType ? mMimeTypeOverride : VoidString(), std::move(stack), profiler_capture_backtrace()); ++mProxy->mOpenCount; runnable->Dispatch(workerPrivate, Canceling, 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) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); if (mCanceled) { aRv.ThrowUncatchableException(); return; } if (!mProxy) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); RefPtr runnable = new SetRequestHeaderRunnable(workerPrivate, mProxy, aHeader, aValue); runnable->Dispatch(workerPrivate, Canceling, aRv); } void XMLHttpRequestWorker::SetTimeout(uint32_t aTimeout, ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); if (mCanceled) { aRv.ThrowUncatchableException(); return; } mTimeout = aTimeout; if (!mProxy) { // Open might not have been called yet, in which case we'll handle the // timeout in OpenRunnable. return; } WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); RefPtr runnable = new SetTimeoutRunnable(workerPrivate, mProxy, aTimeout); runnable->Dispatch(workerPrivate, Canceling, aRv); } void XMLHttpRequestWorker::SetWithCredentials(bool aWithCredentials, ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); if (mCanceled) { aRv.ThrowUncatchableException(); return; } mWithCredentials = aWithCredentials; if (!mProxy) { // Open might not have been called yet, in which case we'll handle the // credentials in OpenRunnable. return; } WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); RefPtr runnable = new SetWithCredentialsRunnable(workerPrivate, mProxy, aWithCredentials); runnable->Dispatch(workerPrivate, Canceling, aRv); } void XMLHttpRequestWorker::SetMozBackgroundRequest(bool aBackgroundRequest, ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); if (mCanceled) { aRv.ThrowUncatchableException(); return; } mBackgroundRequest = aBackgroundRequest; if (!mProxy) { // Open might not have been called yet, in which case we'll handle the // background request in OpenRunnable. return; } WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); RefPtr runnable = new SetBackgroundRequestRunnable(workerPrivate, mProxy, aBackgroundRequest); runnable->Dispatch(workerPrivate, Canceling, aRv); } XMLHttpRequestUpload* XMLHttpRequestWorker::GetUpload(ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); if (mCanceled) { aRv.ThrowUncatchableException(); return nullptr; } if (!mUpload) { mUpload = new XMLHttpRequestUpload(this); } return mUpload; } void XMLHttpRequestWorker::Send( const Nullable< DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString>& aData, ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("Send()")); if (mFlagSendActive) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT); return; } mFlagSendActive = true; auto clearRecursionFlag = MakeScopeExit([&]() { // No one else should have touched this flag. MOZ_ASSERT(mFlagSendActive); mFlagSendActive = false; }); if (mCanceled) { aRv.ThrowUncatchableException(); return; } if (mStateData->mReadyState != XMLHttpRequest_Binding::OPENED) { aRv.ThrowInvalidStateError("XMLHttpRequest state must be OPENED."); return; } if (!mProxy || !mProxy->mXMLHttpRequestPrivate || mFlagSend) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } if (aData.IsNull()) { SendInternal(nullptr, aRv); return; } if (aData.Value().IsDocument()) { MOZ_ASSERT_UNREACHABLE("Documents are not exposed to workers."); aRv.Throw(NS_ERROR_FAILURE); return; } if (aData.Value().IsBlob()) { BodyExtractor body(&aData.Value().GetAsBlob()); SendInternal(&body, aRv); return; } if (aData.Value().IsArrayBuffer()) { BodyExtractor body(&aData.Value().GetAsArrayBuffer()); SendInternal(&body, aRv); return; } if (aData.Value().IsArrayBufferView()) { BodyExtractor body( &aData.Value().GetAsArrayBufferView()); SendInternal(&body, aRv); return; } if (aData.Value().IsFormData()) { BodyExtractor body(&aData.Value().GetAsFormData()); SendInternal(&body, aRv); return; } if (aData.Value().IsURLSearchParams()) { BodyExtractor body( &aData.Value().GetAsURLSearchParams()); SendInternal(&body, aRv); return; } if (aData.Value().IsUSVString()) { BodyExtractor body(&aData.Value().GetAsUSVString()); SendInternal(&body, aRv); return; } } void XMLHttpRequestWorker::Abort(ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); if (mCanceled) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("Abort(canceled)")); aRv.ThrowUncatchableException(); return; } if (!mProxy) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("Abort(no proxy)")); return; } // Spec step 1 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("Abort(step 1))")); mEventStreamId++; WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); RefPtr runnable = new AbortRunnable(workerPrivate, mProxy); runnable->Dispatch(workerPrivate, Canceling, aRv); // Spec step 2 if ((mStateData->mReadyState == XMLHttpRequest_Binding::OPENED && mFlagSend) || mStateData->mReadyState == XMLHttpRequest_Binding::HEADERS_RECEIVED || mStateData->mReadyState == XMLHttpRequest_Binding::LOADING) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("Abort(step 2)")); RequestErrorSteps(aRv, Events::abort); if (aRv.Failed()) { return; } } // Spec step 3 if (mStateData->mReadyState == XMLHttpRequest_Binding::DONE) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("Abort(step 3)")); mStateData->mReadyState = XMLHttpRequest_Binding::UNSENT; } } void XMLHttpRequestWorker::GetResponseHeader(const nsACString& aHeader, nsACString& aResponseHeader, ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); if (mCanceled) { aRv.ThrowUncatchableException(); return; } if (!mProxy) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); nsCString responseHeader; RefPtr runnable = new GetResponseHeaderRunnable( workerPrivate, mProxy, aHeader, responseHeader); runnable->Dispatch(workerPrivate, Canceling, aRv); if (aRv.Failed()) { return; } aResponseHeader = responseHeader; } void XMLHttpRequestWorker::GetAllResponseHeaders(nsACString& aResponseHeaders, ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); if (mCanceled) { aRv.ThrowUncatchableException(); return; } if (!mProxy) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); nsCString responseHeaders; RefPtr runnable = new GetAllResponseHeadersRunnable(workerPrivate, mProxy, responseHeaders); runnable->Dispatch(workerPrivate, Canceling, aRv); if (aRv.Failed()) { return; } aResponseHeaders = responseHeaders; } void XMLHttpRequestWorker::OverrideMimeType(const nsAString& aMimeType, ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); if (mCanceled) { aRv.ThrowUncatchableException(); return; } // We're supposed to throw if the state is LOADING or DONE. if (mStateData->mReadyState == XMLHttpRequest_Binding::LOADING || mStateData->mReadyState == XMLHttpRequest_Binding::DONE) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } mMimeTypeOverride = aMimeType; if (mProxy) { WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); RefPtr runnable = new OverrideMimeTypeRunnable(workerPrivate, mProxy, aMimeType); runnable->Dispatch(workerPrivate, Canceling, aRv); } } void XMLHttpRequestWorker::SetResponseType( XMLHttpRequestResponseType aResponseType, ErrorResult& aRv) { MOZ_ASSERT_DEBUG_OR_FUZZING(IsCurrentThreadRunningWorker()); // "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 (mStateData->mReadyState == XMLHttpRequest_Binding::LOADING || mStateData->mReadyState == XMLHttpRequest_Binding::DONE) { aRv.ThrowInvalidStateError( "Cannot set 'responseType' property on XMLHttpRequest after 'send()' " "(when its state is LOADING or DONE)."); return; } WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); RefPtr runnable = new SetResponseTypeRunnable(workerPrivate, mProxy, aResponseType); runnable->Dispatch(workerPrivate, Canceling, aRv); if (aRv.Failed()) { return; } mResponseType = runnable->ResponseType(); } void XMLHttpRequestWorker::GetResponse(JSContext* aCx, JS::MutableHandle aResponse, ErrorResult& aRv) { if (NS_FAILED(mResponseData->mResponseResult)) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetResponse(none)")); aRv.Throw(mResponseData->mResponseResult); return; } switch (mResponseType) { case XMLHttpRequestResponseType::_empty: case XMLHttpRequestResponseType::Text: { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetResponse(text)")); JSString* str; if (mResponseData->mResponseText.IsEmpty()) { aResponse.set(JS_GetEmptyStringValue(aCx)); return; } str = mResponseData->mResponseText.GetAsJSStringCopy(aCx); if (!str) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } aResponse.setString(str); return; } case XMLHttpRequestResponseType::Arraybuffer: { if (!mResponseData->mResponseArrayBufferBuilder) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetResponse(arraybuffer, null)")); aResponse.setNull(); return; } if (!mResponseArrayBufferValue) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetResponse(arraybuffer)")); mResponseArrayBufferValue = mResponseData->mResponseArrayBufferBuilder->TakeArrayBuffer(aCx); if (!mResponseArrayBufferValue) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } } aResponse.setObject(*mResponseArrayBufferValue); return; } case XMLHttpRequestResponseType::Blob: { if (!mResponseData->mResponseBlobImpl) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetResponse(blob, none)")); aResponse.setNull(); return; } if (!mResponseBlob) { mResponseBlob = Blob::Create(GetOwnerGlobal(), mResponseData->mResponseBlobImpl); } if (!mResponseBlob || !GetOrCreateDOMReflector(aCx, mResponseBlob, aResponse)) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetResponse(blob, null)")); aResponse.setNull(); } else { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetResponse(blob)")); } return; } case XMLHttpRequestResponseType::Json: { if (mResponseData->mResponseJSON.IsVoid()) { aResponse.setNull(); MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetResponse(json, none)")); return; } if (mResponseJSONValue.isUndefined()) { // The Unicode converter has already zapped the BOM if there was one JS::Rooted value(aCx); if (!JS_ParseJSON(aCx, mResponseData->mResponseJSON.BeginReading(), mResponseData->mResponseJSON.Length(), &value)) { JS_ClearPendingException(aCx); MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetResponse(json, null)")); mResponseJSONValue.setNull(); } else { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetResponse(json)")); mResponseJSONValue = value; } mResponseData->mResponseJSON.Truncate(); } aResponse.set(mResponseJSONValue); return; } default: MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("GetResponse(invalid type)")); MOZ_ASSERT_UNREACHABLE("Invalid type"); aResponse.setNull(); return; } } void XMLHttpRequestWorker::GetResponseText(DOMString& aResponseText, ErrorResult& aRv) { MOZ_DIAGNOSTIC_ASSERT(mResponseData); if (mResponseType != XMLHttpRequestResponseType::_empty && mResponseType != XMLHttpRequestResponseType::Text) { aRv.ThrowInvalidStateError( "responseText is only available if responseType is '' or 'text'."); return; } if (!mResponseData->mResponseText.GetAsString(aResponseText)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } } void XMLHttpRequestWorker::UpdateState( UniquePtr&& aStateData, UniquePtr&& aResponseData) { mStateData = std::move(aStateData); UniquePtr responseData = std::move(aResponseData); if (responseData) { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("UpdateState(readyState=%u, new response data)", mStateData->mReadyState)); ResetResponseData(); mResponseData = std::move(responseData); } else { MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("UpdateState(readyState=%u)", mStateData->mReadyState)); } XMLHttpRequest_Binding::ClearCachedResponseTextValue(this); } void XMLHttpRequestWorker::ResetResponseData() { mResponseBlob = nullptr; mResponseArrayBufferValue = nullptr; mResponseJSONValue.setUndefined(); } } // namespace mozilla::dom