summaryrefslogtreecommitdiffstats
path: root/dom/xhr/XMLHttpRequestWorker.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/xhr/XMLHttpRequestWorker.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/xhr/XMLHttpRequestWorker.cpp')
-rw-r--r--dom/xhr/XMLHttpRequestWorker.cpp2233
1 files changed, 2233 insertions, 0 deletions
diff --git a/dom/xhr/XMLHttpRequestWorker.cpp b/dom/xhr/XMLHttpRequestWorker.cpp
new file mode 100644
index 0000000000..3916ec3a92
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestWorker.cpp
@@ -0,0 +1,2233 @@
+/* -*- 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 "jsapi.h" // JS::RootedValueArray
+#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 "mozilla/Telemetry.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsJSUtils.h"
+#include "nsThreadUtils.h"
+
+#include "XMLHttpRequestMainThread.h"
+#include "XMLHttpRequestUpload.h"
+
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla::dom {
+
+/**
+ * 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;
+ const ClientInfo mClientInfo;
+ const Maybe<ServiceWorkerDescriptor> mController;
+
+ // Only ever dereferenced and/or checked on the worker thread. Cleared
+ // explicitly on the worker thread inside XMLHttpRequestWorker::ReleaseProxy.
+ WeakPtr<XMLHttpRequestWorker> mXMLHttpRequestPrivate;
+
+ // XHR Params:
+ bool mMozAnon;
+ bool mMozSystem;
+
+ // Only touched on the main thread.
+ RefPtr<XMLHttpRequestMainThread> mXHR;
+ RefPtr<XMLHttpRequestUpload> 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;
+ bool mDispatchPrematureAbortEvent;
+ bool mDispatchPrematureAbortEventToUpload;
+
+ // Only touched on the main thread.
+ bool mUploadEventListenersAttached;
+ bool mMainThreadSeenLoadStart;
+ bool mInOpen;
+
+ public:
+ Proxy(XMLHttpRequestWorker* aXHRPrivate, const ClientInfo& aClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController, bool aMozAnon,
+ bool aMozSystem)
+ : mWorkerPrivate(nullptr),
+ mClientInfo(aClientInfo),
+ mController(aController),
+ 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),
+ mDispatchPrematureAbortEvent(false),
+ mDispatchPrematureAbortEventToUpload(false),
+ mUploadEventListenersAttached(false),
+ mMainThreadSeenLoadStart(false),
+ mInOpen(false) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ bool Init();
+
+ void Teardown();
+
+ 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, "XHR"_ns),
+ mProxy(aProxy),
+ mErrorCode(NS_OK) {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aProxy);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ void Dispatch(WorkerStatus aFailStatus, ErrorResult& aRv) {
+ WorkerMainThreadRunnable::Dispatch(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<BlobImpl> mBlobImpl;
+ nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+ bool mHasUploadListeners;
+
+ public:
+ SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ BlobImpl* aBlobImpl)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mBlobImpl(aBlobImpl),
+ mHasUploadListeners(false) {}
+
+ void SetHaveUploadListeners(bool aHasUploadListeners) {
+ mHasUploadListeners = aHasUploadListeners;
+ }
+
+ void SetSyncLoopTarget(nsIEventTarget* aSyncLoopTarget) {
+ mSyncLoopTarget = aSyncLoopTarget;
+ }
+
+ private:
+ ~SendRunnable() = default;
+
+ virtual void RunOnMainThread(ErrorResult& aRv) override;
+};
+
+namespace {
+
+enum {
+ STRING_abort = 0,
+ STRING_error,
+ STRING_load,
+ STRING_loadstart,
+ STRING_progress,
+ STRING_timeout,
+ STRING_loadend,
+ STRING_readystatechange,
+
+ STRING_COUNT,
+
+ STRING_LAST_XHR = STRING_readystatechange,
+ STRING_LAST_EVENTTARGET = STRING_loadend
+};
+
+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[] = {
+ // XMLHttpRequestEventTarget event types, supported by both XHR and Upload.
+ "abort",
+ "error",
+ "load",
+ "loadstart",
+ "progress",
+ "timeout",
+ "loadend",
+
+ // XMLHttpRequest event types, supported only by XHR.
+ "readystatechange",
+};
+
+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() = default;
+};
+
+class AsyncTeardownRunnable final : public Runnable {
+ RefPtr<Proxy> 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 {
+ WorkerPrivate* mWorkerPrivate;
+ RefPtr<Proxy> mProxy;
+ RefPtr<XMLHttpRequest> mXHR;
+ nsString mEventType;
+ 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),
+ 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 {
+ // We need to check first if cancel is called twice
+ nsresult rv = MainThreadProxyRunnable::Cancel();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // On the first cancel, this must run!
+ return Run();
+ }
+ };
+
+ public:
+ explicit LoadStartDetectionRunnable(Proxy* aProxy)
+ : Runnable("dom::LoadStartDetectionRunnable"),
+ mWorkerPrivate(aProxy->mWorkerPrivate),
+ mProxy(aProxy),
+ mXHR(aProxy->mXHR),
+ mChannelId(mProxy->mInnerChannelId),
+ mReceivedLoadStart(false) {
+ AssertIsOnMainThread();
+ mEventType.AssignASCII(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))) {
+ NS_WARNING("Failed to add event listener!");
+ return false;
+ }
+
+ return NS_SUCCEEDED(mWorkerPrivate->DispatchToMainThread(this));
+ }
+
+ private:
+ ~LoadStartDetectionRunnable() { AssertIsOnMainThread(); }
+};
+
+class EventRunnable final : public MainThreadProxyRunnable {
+ nsString mType;
+ UniquePtr<XMLHttpRequestWorker::ResponseData> mResponseData;
+ 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;
+ nsresult mStatusResult;
+ // 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),
+ 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),
+ mScopeObj(RootingCx(), aScopeObj) {}
+
+ EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
+ JS::Handle<JSObject*> aScopeObj)
+ : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy),
+ 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),
+ 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;
+ nsString mURL;
+ Optional<nsAString> mUser;
+ nsString mUserStr;
+ Optional<nsAString> mPassword;
+ nsString 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<SerializedStackHolder> mOriginStack;
+
+ // Remember the worker thread's stack when the XHR was opened for profiling
+ // purposes.
+ UniquePtr<ProfileChunkedBuffer> mSource;
+
+ 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,
+ const nsString& aMimeTypeOverride,
+ UniquePtr<SerializedStackHolder> aOriginStack,
+ UniquePtr<ProfileChunkedBuffer> 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 {
+ WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
+ mProxy->mWorkerPrivate = mWorkerPrivate;
+
+ MainThreadRunInternal(aRv);
+
+ mProxy->mWorkerPrivate = 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() {
+ 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;
+ }
+
+ mXHR = new XMLHttpRequestMainThread(ownerWindow ? ownerWindow->AsGlobal()
+ : nullptr);
+ mXHR->Construct(mWorkerPrivate->GetPrincipal(),
+ mWorkerPrivate->CookieJarSettings(), true,
+ mWorkerPrivate->GetBaseURI(), mWorkerPrivate->GetLoadGroup(),
+ mWorkerPrivate->GetPerformanceStorage(),
+ mWorkerPrivate->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<MainThreadStopSyncLoopRunnable> runnable =
+ new MainThreadStopSyncLoopRunnable(
+ mWorkerPrivate, std::move(mSyncLoopTarget), NS_ERROR_FAILURE);
+ MOZ_ALWAYS_TRUE(runnable->Dispatch());
+ }
+
+ mOutstandingSendCount = 0;
+ }
+
+ mWorkerPrivate = nullptr;
+ mXHRUpload = nullptr;
+ mXHR = nullptr;
+ }
+
+ MOZ_ASSERT(!mWorkerPrivate);
+ MOZ_ASSERT(!mSyncLoopTarget);
+ // If there are rare edge cases left that violate our invariants
+ // just ensure that they won't harm us too much.
+ mWorkerPrivate = 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<DOMEventTargetHelper> targetHelper =
+ aUpload ? static_cast<XMLHttpRequestUpload*>(mXHRUpload.get())
+ : static_cast<XMLHttpRequestEventTarget*>(mXHR.get());
+ MOZ_ASSERT(targetHelper, "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(targetHelper->AddEventListener(eventType, this, false))) {
+ return false;
+ }
+ } else {
+ targetHelper->RemoveEventListener(eventType, 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 (!mWorkerPrivate) {
+ NS_ERROR("Shouldn't get here!");
+ return NS_OK;
+ }
+
+ nsAutoString type;
+ aEvent->GetType(type);
+
+ bool isUploadTarget = mXHR != aEvent->GetTarget();
+ ProgressEvent* progressEvent = aEvent->AsProgressEvent();
+
+ if (mInOpen && type.EqualsASCII(sEventStrings[STRING_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<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) {
+ if (!mIsSyncXHR || !type.EqualsASCII(sEventStrings[STRING_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();
+ }
+ }
+
+ if (!isUploadTarget) {
+ if (type.EqualsASCII(sEventStrings[STRING_loadstart])) {
+ mMainThreadSeenLoadStart = true;
+ } else if (mMainThreadSeenLoadStart &&
+ type.EqualsASCII(sEventStrings[STRING_loadend])) {
+ mMainThreadSeenLoadStart = false;
+
+ RefPtr<LoadStartDetectionRunnable> 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(mEventType, this, false);
+
+ if (!mReceivedLoadStart) {
+ if (mProxy->mOutstandingSendCount > 1) {
+ mProxy->mOutstandingSendCount--;
+ } else if (mProxy->mOutstandingSendCount == 1) {
+ mProxy->Reset();
+
+ RefPtr<ProxyCompleteRunnable> runnable =
+ new ProxyCompleteRunnable(mWorkerPrivate, mProxy, mChannelId);
+ if (runnable->Dispatch()) {
+ mProxy->mWorkerPrivate = 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 == mEventType);
+ }
+#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);
+
+ ErrorResult rv;
+
+ XMLHttpRequestResponseType type = xhr->ResponseType();
+
+ // We want to take the result data only if this is available.
+ if (mType.EqualsASCII(sEventStrings[STRING_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();
+
+ 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;
+ if (mProxy->mDispatchPrematureAbortEventToUpload) {
+ // We've already dispatched premature abort events.
+ return true;
+ }
+ } else {
+ mProxy->mSeenLoadStart = false;
+ if (mProxy->mDispatchPrematureAbortEvent) {
+ // We've already dispatched premature abort events.
+ return true;
+ }
+ }
+ } else if (mType.EqualsASCII(sEventStrings[STRING_abort])) {
+ if ((mUploadEvent && mProxy->mDispatchPrematureAbortEventToUpload) ||
+ (!mUploadEvent && mProxy->mDispatchPrematureAbortEvent)) {
+ // 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;
+ }
+ }
+
+ UniquePtr<XMLHttpRequestWorker::StateData> 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.EqualsASCII(sEventStrings[STRING_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> 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->DispatchEvent(*event);
+
+ 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();
+}
+
+void OpenRunnable::MainThreadRunInternal(ErrorResult& aRv) {
+ if (!mProxy->Init()) {
+ 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() : VoidString(),
+ mPassword.WasPassed() ? mPassword.Value() : VoidString(), 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<JSObject*> globalObject(RootingCx(),
+ xpc::UnprivilegedJunkScope(fallible));
+ 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;
+ }
+
+ RefPtr<Blob> 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->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->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;
+ mSyncLoopTarget = nullptr;
+ }
+}
+
+XMLHttpRequestWorker::XMLHttpRequestWorker(WorkerPrivate* aWorkerPrivate,
+ nsIGlobalObject* aGlobalObject)
+ : XMLHttpRequest(aGlobalObject),
+ mWorkerPrivate(aWorkerPrivate),
+ 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()) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ mozilla::HoldJSObjects(this);
+}
+
+XMLHttpRequestWorker::~XMLHttpRequestWorker() {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ 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<XMLHttpRequest> XMLHttpRequestWorker::Construct(
+ const GlobalObject& aGlobal, const MozXMLHttpRequestParameters& aParams,
+ ErrorResult& aRv) {
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+ MOZ_ASSERT(workerPrivate);
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (NS_WARN_IF(!global)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<XMLHttpRequestWorker> xhr =
+ new XMLHttpRequestWorker(workerPrivate, global);
+
+ 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) {
+ // 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<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++;
+ }
+
+ // Ensure we are unpinned before we clear the weak reference.
+ RefPtr<XMLHttpRequestWorker> self = this;
+ if (mPinnedSelfRef) {
+ Unpin();
+ }
+ mProxy->mXMLHttpRequestPrivate = nullptr;
+
+ // We need to make a sync call here.
+ RefPtr<SyncTeardownRunnable> runnable =
+ new SyncTeardownRunnable(mWorkerPrivate, mProxy);
+ mProxy = nullptr;
+
+ IgnoredErrorResult forAssertionsOnly;
+ // This runnable _must_ be executed.
+ runnable->Dispatch(Dead, forAssertionsOnly);
+ MOZ_DIAGNOSTIC_ASSERT(!forAssertionsOnly.Failed());
+ }
+ }
+}
+
+void XMLHttpRequestWorker::MaybePin(ErrorResult& aRv) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mWorkerRef) {
+ return;
+ }
+
+ RefPtr<XMLHttpRequestWorker> self = this;
+ mWorkerRef =
+ StrongWorkerRef::Create(mWorkerPrivate, "XMLHttpRequestWorker", [self]() {
+ if (!self->mCanceled) {
+ self->mCanceled = true;
+ self->ReleaseProxy(WorkerIsGoingAway);
+ }
+ });
+ if (NS_WARN_IF(!mWorkerRef)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mPinnedSelfRef = this;
+}
+
+void XMLHttpRequestWorker::MaybeDispatchPrematureAbortEvents(ErrorResult& aRv) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mProxy);
+
+ // Only send readystatechange event when state changed.
+ bool isStateChanged = false;
+ if ((mStateData->mReadyState == 1 && mStateData->mFlagSend) ||
+ mStateData->mReadyState == 2 || mStateData->mReadyState == 3) {
+ isStateChanged = true;
+ mStateData->mReadyState = 4;
+ }
+
+ if (mProxy->mSeenUploadLoadStart) {
+ MOZ_ASSERT(mUpload);
+
+ DispatchPrematureAbortEvent(mUpload, u"abort"_ns, true, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ DispatchPrematureAbortEvent(mUpload, u"loadend"_ns, true, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Similarly to null check in ::Open, mProxy may have been cleared here.
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ mProxy->mSeenUploadLoadStart = false;
+ mProxy->mDispatchPrematureAbortEventToUpload = true;
+ }
+
+ if (mProxy->mSeenLoadStart) {
+ if (isStateChanged) {
+ DispatchPrematureAbortEvent(this, u"readystatechange"_ns, false, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ DispatchPrematureAbortEvent(this, u"abort"_ns, false, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ DispatchPrematureAbortEvent(this, u"loadend"_ns, false, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Similarly to null check in ::Open, mProxy may have been cleared here.
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ mProxy->mSeenLoadStart = false;
+ mProxy->mDispatchPrematureAbortEvent = true;
+ }
+}
+
+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 {
+ if (mProxy->mIsSyncXHR &&
+ aEventType.EqualsASCII(sEventStrings[STRING_progress])) {
+ return;
+ }
+
+ 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->DispatchEvent(*event);
+}
+
+void XMLHttpRequestWorker::Unpin() {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(mWorkerRef, "Mismatched calls to Unpin!");
+ mWorkerRef = nullptr;
+
+ mPinnedSelfRef = nullptr;
+}
+
+void XMLHttpRequestWorker::SendInternal(const BodyExtractorBase* aBody,
+ ErrorResult& aRv) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ // 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> blobImpl;
+
+ if (aBody) {
+ nsAutoCString charset;
+ nsAutoCString defaultContentType;
+ nsCOMPtr<nsIInputStream> 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);
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, 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<XMLHttpRequestWorker> selfRef = this;
+ AutoUnpinXHR autoUnpin(this);
+ Maybe<AutoSyncLoopHolder> autoSyncLoop;
+
+ nsCOMPtr<nsISerialEventTarget> syncLoopTarget;
+ bool isSyncXHR = mProxy->mIsSyncXHR;
+ if (isSyncXHR) {
+ autoSyncLoop.emplace(mWorkerPrivate, Canceling);
+ syncLoopTarget = autoSyncLoop->GetSerialEventTarget();
+ if (!syncLoopTarget) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ }
+
+ mProxy->mOuterChannelId++;
+ mProxy->mDispatchPrematureAbortEvent = false;
+ mProxy->mDispatchPrematureAbortEventToUpload = false;
+
+ sendRunnable->SetSyncLoopTarget(syncLoopTarget);
+ sendRunnable->SetHaveUploadListeners(hasUploadListeners);
+
+ mStateData->mFlagSend = true;
+
+ sendRunnable->Dispatch(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(!autoSyncLoop);
+ return;
+ }
+
+ autoUnpin.Clear();
+
+ bool succeeded = NS_SUCCEEDED(autoSyncLoop->Run());
+ mStateData->mFlagSend = false;
+
+ // 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()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+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;
+ }
+
+ bool alsoOverrideMimeType = false;
+ if (mProxy) {
+ MaybeDispatchPrematureAbortEvents(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ } else {
+ Maybe<ClientInfo> clientInfo(
+ mWorkerPrivate->GlobalScope()->GetClientInfo());
+ if (clientInfo.isNothing()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ mProxy = new Proxy(this, clientInfo.ref(),
+ mWorkerPrivate->GlobalScope()->GetController(), mMozAnon,
+ mMozSystem);
+ alsoOverrideMimeType = true;
+ }
+
+ mProxy->mOuterEventStreamId++;
+
+ UniquePtr<SerializedStackHolder> stack;
+ if (mWorkerPrivate->IsWatchedByDevTools()) {
+ if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) {
+ stack = GetCurrentStackForNetMonitor(cx);
+ }
+ }
+
+ RefPtr<OpenRunnable> runnable = new OpenRunnable(
+ mWorkerPrivate, mProxy, aMethod, aUrl, aUser, aPassword,
+ mBackgroundRequest, mWithCredentials, mTimeout, mResponseType,
+ alsoOverrideMimeType ? mMimeTypeOverride : VoidString(), std::move(stack),
+ profiler_capture_backtrace());
+
+ ++mProxy->mOpenCount;
+ runnable->Dispatch(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) {
+ 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(Canceling, 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(Canceling, 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(Canceling, 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(Canceling, aRv);
+}
+
+XMLHttpRequestUpload* XMLHttpRequestWorker::GetUpload(ErrorResult& aRv) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return nullptr;
+ }
+
+ if (!mUpload) {
+ mUpload = new XMLHttpRequestUpload(this);
+ }
+
+ return mUpload;
+}
+
+void XMLHttpRequestWorker::Send(
+ const Nullable<
+ DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString>&
+ aData,
+ ErrorResult& aRv) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ 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 || mStateData->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<const Blob> body(&aData.Value().GetAsBlob());
+ SendInternal(&body, aRv);
+ return;
+ }
+
+ if (aData.Value().IsArrayBuffer()) {
+ BodyExtractor<const ArrayBuffer> body(&aData.Value().GetAsArrayBuffer());
+ SendInternal(&body, aRv);
+ return;
+ }
+
+ if (aData.Value().IsArrayBufferView()) {
+ BodyExtractor<const ArrayBufferView> body(
+ &aData.Value().GetAsArrayBufferView());
+ SendInternal(&body, aRv);
+ return;
+ }
+
+ if (aData.Value().IsFormData()) {
+ BodyExtractor<const FormData> body(&aData.Value().GetAsFormData());
+ SendInternal(&body, aRv);
+ return;
+ }
+
+ if (aData.Value().IsURLSearchParams()) {
+ BodyExtractor<const URLSearchParams> body(
+ &aData.Value().GetAsURLSearchParams());
+ SendInternal(&body, aRv);
+ return;
+ }
+
+ if (aData.Value().IsUSVString()) {
+ BodyExtractor<const nsAString> body(&aData.Value().GetAsUSVString());
+ SendInternal(&body, aRv);
+ return;
+ }
+}
+
+void XMLHttpRequestWorker::Abort(ErrorResult& aRv) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ return;
+ }
+
+ // Set our status to 0 and statusText to "" if we
+ // will be aborting an ongoing fetch, so the upcoming
+ // abort events we dispatch have the correct info.
+ if ((mStateData->mReadyState == XMLHttpRequest_Binding::OPENED &&
+ mStateData->mFlagSend) ||
+ mStateData->mReadyState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
+ mStateData->mReadyState == XMLHttpRequest_Binding::LOADING ||
+ mStateData->mReadyState == XMLHttpRequest_Binding::DONE) {
+ mStateData->mStatus = 0;
+ mStateData->mStatusText.Truncate();
+ }
+
+ 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(Canceling, 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(Canceling, 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(Canceling, 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 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) {
+ RefPtr<OverrideMimeTypeRunnable> runnable =
+ new OverrideMimeTypeRunnable(mWorkerPrivate, mProxy, aMimeType);
+ runnable->Dispatch(Canceling, aRv);
+ }
+}
+
+void XMLHttpRequestWorker::SetResponseType(
+ XMLHttpRequestResponseType aResponseType, ErrorResult& aRv) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ // "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;
+ }
+
+ RefPtr<SetResponseTypeRunnable> runnable =
+ new SetResponseTypeRunnable(mWorkerPrivate, mProxy, aResponseType);
+ runnable->Dispatch(Canceling, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ mResponseType = runnable->ResponseType();
+}
+
+void XMLHttpRequestWorker::GetResponse(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResponse,
+ ErrorResult& aRv) {
+ if (NS_FAILED(mResponseData->mResponseResult)) {
+ aRv.Throw(mResponseData->mResponseResult);
+ return;
+ }
+
+ switch (mResponseType) {
+ case XMLHttpRequestResponseType::_empty:
+ case XMLHttpRequestResponseType::Text: {
+ JSString* str;
+
+ if (mResponseData->mResponseText.IsEmpty()) {
+ aResponse.set(JS_GetEmptyStringValue(aCx));
+ return;
+ }
+
+ XMLHttpRequestStringSnapshotReaderHelper helper(
+ mResponseData->mResponseText);
+
+ str = JS_NewUCStringCopyN(aCx, helper.Buffer(), helper.Length());
+ if (!str) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ aResponse.setString(str);
+ return;
+ }
+
+ case XMLHttpRequestResponseType::Arraybuffer: {
+ if (!mResponseData->mResponseArrayBufferBuilder) {
+ aResponse.setNull();
+ return;
+ }
+
+ if (!mResponseArrayBufferValue) {
+ 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) {
+ aResponse.setNull();
+ return;
+ }
+
+ if (!mResponseBlob) {
+ mResponseBlob =
+ Blob::Create(GetOwnerGlobal(), mResponseData->mResponseBlobImpl);
+ }
+
+ if (!mResponseBlob ||
+ !GetOrCreateDOMReflector(aCx, mResponseBlob, aResponse)) {
+ aResponse.setNull();
+ }
+
+ return;
+ }
+
+ case XMLHttpRequestResponseType::Json: {
+ if (mResponseData->mResponseJSON.IsVoid()) {
+ aResponse.setNull();
+ return;
+ }
+
+ if (mResponseJSONValue.isUndefined()) {
+ // The Unicode converter has already zapped the BOM if there was one
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS_ParseJSON(aCx, mResponseData->mResponseJSON.BeginReading(),
+ mResponseData->mResponseJSON.Length(), &value)) {
+ JS_ClearPendingException(aCx);
+ mResponseJSONValue.setNull();
+ } else {
+ mResponseJSONValue = value;
+ }
+
+ mResponseData->mResponseJSON.Truncate();
+ }
+
+ aResponse.set(mResponseJSONValue);
+ return;
+ }
+
+ default:
+ 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<StateData>&& aStateData,
+ UniquePtr<ResponseData>&& aResponseData) {
+ mStateData = std::move(aStateData);
+
+ UniquePtr<ResponseData> responseData = std::move(aResponseData);
+ if (responseData) {
+ ResetResponseData();
+ mResponseData = std::move(responseData);
+ }
+
+ XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
+}
+
+void XMLHttpRequestWorker::ResetResponseData() {
+ mResponseBlob = nullptr;
+ mResponseArrayBufferValue = nullptr;
+ mResponseJSONValue.setUndefined();
+}
+
+} // namespace mozilla::dom