1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "XMLHttpRequestWorker.h"
9 #include "nsIDOMEventListener.h"
11 #include "GeckoProfiler.h"
12 #include "jsapi.h" // JS::RootedValueArray
13 #include "jsfriendapi.h"
14 #include "js/ArrayBuffer.h" // JS::Is{,Detached}ArrayBufferObject
15 #include "js/GCPolicyAPI.h"
17 #include "js/RootingAPI.h" // JS::{Handle,Heap},PersistentRooted
18 #include "js/TracingAPI.h"
19 #include "js/Value.h" // JS::{Undefined,}Value
20 #include "mozilla/ArrayUtils.h"
21 #include "mozilla/HoldDropJSObjects.h"
22 #include "mozilla/dom/Exceptions.h"
23 #include "mozilla/dom/Event.h"
24 #include "mozilla/dom/File.h"
25 #include "mozilla/dom/FormData.h"
26 #include "mozilla/dom/ProgressEvent.h"
27 #include "mozilla/dom/SerializedStackHolder.h"
28 #include "mozilla/dom/StreamBlobImpl.h"
29 #include "mozilla/dom/StructuredCloneHolder.h"
30 #include "mozilla/dom/URLSearchParams.h"
31 #include "mozilla/dom/WorkerScope.h"
32 #include "mozilla/dom/WorkerRef.h"
33 #include "mozilla/dom/WorkerRunnable.h"
34 #include "mozilla/dom/XMLHttpRequestBinding.h"
35 #include "mozilla/Telemetry.h"
36 #include "nsComponentManagerUtils.h"
37 #include "nsContentUtils.h"
38 #include "nsJSUtils.h"
39 #include "nsThreadUtils.h"
41 #include "XMLHttpRequestMainThread.h"
42 #include "XMLHttpRequestUpload.h"
44 #include "mozilla/UniquePtr.h"
46 namespace mozilla::dom
{
49 * XMLHttpRequest in workers
51 * XHR in workers is implemented by proxying calls/events/etc between the
52 * worker thread and an XMLHttpRequest on the main thread. The glue
53 * object here is the Proxy, which lives on both threads. All other objects
54 * live on either the main thread (the XMLHttpRequest) or the worker thread
55 * (the worker and XHR private objects).
57 * The main thread XHR is always operated in async mode, even for sync XHR
58 * in workers. Calls made on the worker thread are proxied to the main thread
59 * synchronously (meaning the worker thread is blocked until the call
60 * returns). Each proxied call spins up a sync queue, which captures any
61 * synchronously dispatched events and ensures that they run synchronously
62 * on the worker as well. Asynchronously dispatched events are posted to the
63 * worker thread to run asynchronously. Some of the XHR state is mirrored on
64 * the worker thread to avoid needing a cross-thread call on every property
67 * The XHR private is stored in the private slot of the XHR JSObject on the
68 * worker thread. It is destroyed when that JSObject is GCd. The private
69 * roots its JSObject while network activity is in progress. It also
70 * adds itself as a feature to the worker to give itself a chance to clean up
71 * if the worker goes away during an XHR call. It is important that the
72 * rooting and feature registration (collectively called pinning) happens at
73 * the proper times. If we pin for too long we can cause memory leaks or even
74 * shutdown hangs. If we don't pin for long enough we introduce a GC hazard.
76 * The XHR is pinned from the time Send is called to roughly the time loadend
77 * is received. There are some complications involved with Abort and XHR
78 * reuse. We maintain a counter on the main thread of how many times Send was
79 * called on this XHR, and we decrement the counter every time we receive a
80 * loadend event. When the counter reaches zero we dispatch a runnable to the
81 * worker thread to unpin the XHR. We only decrement the counter if the
82 * dispatch was successful, because the worker may no longer be accepting
83 * regular runnables. In the event that we reach Proxy::Teardown and there
84 * the outstanding Send count is still non-zero, we dispatch a control
85 * runnable which is guaranteed to run.
87 * NB: Some of this could probably be simplified now that we have the
88 * inner/outer channel ids.
91 class Proxy final
: public nsIDOMEventListener
{
93 // Read on multiple threads.
94 WorkerPrivate
* mWorkerPrivate
;
95 XMLHttpRequestWorker
* mXMLHttpRequestPrivate
;
96 const ClientInfo mClientInfo
;
97 const Maybe
<ServiceWorkerDescriptor
> mController
;
103 // Only touched on the main thread.
104 RefPtr
<XMLHttpRequestMainThread
> mXHR
;
105 RefPtr
<XMLHttpRequestUpload
> mXHRUpload
;
106 nsCOMPtr
<nsIEventTarget
> mSyncLoopTarget
;
107 nsCOMPtr
<nsIEventTarget
> mSyncEventResponseTarget
;
108 uint32_t mInnerEventStreamId
;
109 uint32_t mInnerChannelId
;
110 uint32_t mOutstandingSendCount
;
112 // Only touched on the worker thread.
113 uint32_t mOuterEventStreamId
;
114 uint32_t mOuterChannelId
;
116 uint64_t mLastLoaded
;
118 uint64_t mLastUploadLoaded
;
119 uint64_t mLastUploadTotal
;
121 bool mLastLengthComputable
;
122 bool mLastUploadLengthComputable
;
124 bool mSeenUploadLoadStart
;
125 bool mDispatchPrematureAbortEvent
;
126 bool mDispatchPrematureAbortEventToUpload
;
128 // Only touched on the main thread.
129 bool mUploadEventListenersAttached
;
130 bool mMainThreadSeenLoadStart
;
134 Proxy(XMLHttpRequestWorker
* aXHRPrivate
, const ClientInfo
& aClientInfo
,
135 const Maybe
<ServiceWorkerDescriptor
>& aController
, bool aMozAnon
,
137 : mWorkerPrivate(nullptr),
138 mXMLHttpRequestPrivate(aXHRPrivate
),
139 mClientInfo(aClientInfo
),
140 mController(aController
),
142 mMozSystem(aMozSystem
),
143 mInnerEventStreamId(0),
145 mOutstandingSendCount(0),
146 mOuterEventStreamId(0),
151 mLastUploadLoaded(0),
154 mLastLengthComputable(false),
155 mLastUploadLengthComputable(false),
156 mSeenLoadStart(false),
157 mSeenUploadLoadStart(false),
158 mDispatchPrematureAbortEvent(false),
159 mDispatchPrematureAbortEventToUpload(false),
160 mUploadEventListenersAttached(false),
161 mMainThreadSeenLoadStart(false),
164 NS_DECL_THREADSAFE_ISUPPORTS
165 NS_DECL_NSIDOMEVENTLISTENER
169 void Teardown(bool aSendUnpin
);
171 bool AddRemoveEventListeners(bool aUpload
, bool aAdd
);
174 AssertIsOnMainThread();
176 if (mUploadEventListenersAttached
) {
177 AddRemoveEventListeners(true, false);
181 already_AddRefed
<nsIEventTarget
> GetEventTarget() {
182 AssertIsOnMainThread();
184 nsCOMPtr
<nsIEventTarget
> target
=
185 mSyncEventResponseTarget
? mSyncEventResponseTarget
: mSyncLoopTarget
;
186 return target
.forget();
192 MOZ_ASSERT(!mXHRUpload
);
193 MOZ_ASSERT(!mOutstandingSendCount
);
197 class WorkerThreadProxySyncRunnable
: public WorkerMainThreadRunnable
{
199 RefPtr
<Proxy
> mProxy
;
202 // mErrorCode is set on the main thread by MainThreadRun and it's used to at
203 // the end of the Dispatch() to return the error code.
207 WorkerThreadProxySyncRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
)
208 : WorkerMainThreadRunnable(aWorkerPrivate
, "XHR"_ns
),
211 MOZ_ASSERT(aWorkerPrivate
);
213 aWorkerPrivate
->AssertIsOnWorkerThread();
216 void Dispatch(WorkerStatus aFailStatus
, ErrorResult
& aRv
) {
217 WorkerMainThreadRunnable::Dispatch(aFailStatus
, aRv
);
218 if (NS_WARN_IF(aRv
.Failed())) {
222 if (NS_FAILED(mErrorCode
)) {
223 aRv
.Throw(mErrorCode
);
228 virtual ~WorkerThreadProxySyncRunnable() = default;
230 virtual void RunOnMainThread(ErrorResult
& aRv
) = 0;
233 virtual bool MainThreadRun() override
;
236 class SendRunnable final
: public WorkerThreadProxySyncRunnable
{
237 RefPtr
<BlobImpl
> mBlobImpl
;
238 nsCOMPtr
<nsIEventTarget
> mSyncLoopTarget
;
239 bool mHasUploadListeners
;
242 SendRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
,
244 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
),
245 mBlobImpl(aBlobImpl
),
246 mHasUploadListeners(false) {}
248 void SetHaveUploadListeners(bool aHasUploadListeners
) {
249 mHasUploadListeners
= aHasUploadListeners
;
252 void SetSyncLoopTarget(nsIEventTarget
* aSyncLoopTarget
) {
253 mSyncLoopTarget
= aSyncLoopTarget
;
257 ~SendRunnable() = default;
259 virtual void RunOnMainThread(ErrorResult
& aRv
) override
;
272 STRING_readystatechange
,
276 STRING_LAST_XHR
= STRING_readystatechange
,
277 STRING_LAST_EVENTTARGET
= STRING_loadend
280 static_assert(STRING_LAST_XHR
>= STRING_LAST_EVENTTARGET
, "Bad string setup!");
281 static_assert(STRING_LAST_XHR
== STRING_COUNT
- 1, "Bad string setup!");
283 const char* const sEventStrings
[] = {
284 // XMLHttpRequestEventTarget event types, supported by both XHR and Upload.
293 // XMLHttpRequest event types, supported only by XHR.
297 static_assert(MOZ_ARRAY_LENGTH(sEventStrings
) == STRING_COUNT
,
298 "Bad string count!");
300 class MainThreadProxyRunnable
: public MainThreadWorkerSyncRunnable
{
302 RefPtr
<Proxy
> mProxy
;
304 MainThreadProxyRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
)
305 : MainThreadWorkerSyncRunnable(aWorkerPrivate
, aProxy
->GetEventTarget()),
310 virtual ~MainThreadProxyRunnable() = default;
313 class XHRUnpinRunnable final
: public MainThreadWorkerControlRunnable
{
314 XMLHttpRequestWorker
* mXMLHttpRequestPrivate
;
317 XHRUnpinRunnable(WorkerPrivate
* aWorkerPrivate
,
318 XMLHttpRequestWorker
* aXHRPrivate
)
319 : MainThreadWorkerControlRunnable(aWorkerPrivate
),
320 mXMLHttpRequestPrivate(aXHRPrivate
) {
321 MOZ_ASSERT(aXHRPrivate
);
325 ~XHRUnpinRunnable() = default;
327 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
328 if (mXMLHttpRequestPrivate
->SendInProgress()) {
329 mXMLHttpRequestPrivate
->Unpin();
336 class AsyncTeardownRunnable final
: public Runnable
{
337 RefPtr
<Proxy
> mProxy
;
340 explicit AsyncTeardownRunnable(Proxy
* aProxy
)
341 : Runnable("dom::AsyncTeardownRunnable"), mProxy(aProxy
) {
346 ~AsyncTeardownRunnable() = default;
350 AssertIsOnMainThread();
352 // This means the XHR was GC'd, so we can't be pinned, and we don't need to
354 mProxy
->Teardown(/* aSendUnpin */ false);
361 class LoadStartDetectionRunnable final
: public Runnable
,
362 public nsIDOMEventListener
{
363 WorkerPrivate
* mWorkerPrivate
;
364 RefPtr
<Proxy
> mProxy
;
365 RefPtr
<XMLHttpRequest
> mXHR
;
366 XMLHttpRequestWorker
* mXMLHttpRequestPrivate
;
369 bool mReceivedLoadStart
;
371 class ProxyCompleteRunnable final
: public MainThreadProxyRunnable
{
372 XMLHttpRequestWorker
* mXMLHttpRequestPrivate
;
376 ProxyCompleteRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
,
377 XMLHttpRequestWorker
* aXHRPrivate
,
379 : MainThreadProxyRunnable(aWorkerPrivate
, aProxy
),
380 mXMLHttpRequestPrivate(aXHRPrivate
),
381 mChannelId(aChannelId
) {}
384 ~ProxyCompleteRunnable() = default;
386 virtual bool WorkerRun(JSContext
* aCx
,
387 WorkerPrivate
* aWorkerPrivate
) override
{
388 if (mChannelId
!= mProxy
->mOuterChannelId
) {
389 // Threads raced, this event is now obsolete.
393 if (mSyncLoopTarget
) {
394 aWorkerPrivate
->StopSyncLoop(mSyncLoopTarget
, NS_OK
);
397 if (mXMLHttpRequestPrivate
->SendInProgress()) {
398 mXMLHttpRequestPrivate
->Unpin();
404 nsresult
Cancel() override
{ return Run(); }
408 LoadStartDetectionRunnable(Proxy
* aProxy
, XMLHttpRequestWorker
* aXHRPrivate
)
409 : Runnable("dom::LoadStartDetectionRunnable"),
410 mWorkerPrivate(aProxy
->mWorkerPrivate
),
413 mXMLHttpRequestPrivate(aXHRPrivate
),
414 mChannelId(mProxy
->mInnerChannelId
),
415 mReceivedLoadStart(false) {
416 AssertIsOnMainThread();
417 mEventType
.AssignASCII(sEventStrings
[STRING_loadstart
]);
420 NS_DECL_ISUPPORTS_INHERITED
422 NS_DECL_NSIDOMEVENTLISTENER
424 bool RegisterAndDispatch() {
425 AssertIsOnMainThread();
427 if (NS_FAILED(mXHR
->AddEventListener(mEventType
, this, false, false))) {
428 NS_WARNING("Failed to add event listener!");
432 return NS_SUCCEEDED(mWorkerPrivate
->DispatchToMainThread(this));
436 ~LoadStartDetectionRunnable() { AssertIsOnMainThread(); }
439 class EventRunnable final
: public MainThreadProxyRunnable
{
441 UniquePtr
<XMLHttpRequestWorker::ResponseData
> mResponseData
;
442 nsString mResponseURL
;
443 nsCString mStatusText
;
446 uint32_t mEventStreamId
;
448 uint16_t mReadyState
;
451 bool mLengthComputable
;
452 nsresult mStatusResult
;
453 // mScopeObj is used in PreDispatch only. We init it in our constructor, and
454 // reset() in PreDispatch, to ensure that it's not still linked into the
455 // runtime once we go off-thread.
456 JS::PersistentRooted
<JSObject
*> mScopeObj
;
459 EventRunnable(Proxy
* aProxy
, bool aUploadEvent
, const nsString
& aType
,
460 bool aLengthComputable
, uint64_t aLoaded
, uint64_t aTotal
,
461 JS::Handle
<JSObject
*> aScopeObj
)
462 : MainThreadProxyRunnable(aProxy
->mWorkerPrivate
, aProxy
),
464 mResponseData(new XMLHttpRequestWorker::ResponseData()),
467 mEventStreamId(aProxy
->mInnerEventStreamId
),
470 mUploadEvent(aUploadEvent
),
471 mProgressEvent(true),
472 mLengthComputable(aLengthComputable
),
473 mStatusResult(NS_OK
),
474 mScopeObj(RootingCx(), aScopeObj
) {}
476 EventRunnable(Proxy
* aProxy
, bool aUploadEvent
, const nsString
& aType
,
477 JS::Handle
<JSObject
*> aScopeObj
)
478 : MainThreadProxyRunnable(aProxy
->mWorkerPrivate
, aProxy
),
480 mResponseData(new XMLHttpRequestWorker::ResponseData()),
483 mEventStreamId(aProxy
->mInnerEventStreamId
),
486 mUploadEvent(aUploadEvent
),
487 mProgressEvent(false),
488 mLengthComputable(0),
489 mStatusResult(NS_OK
),
490 mScopeObj(RootingCx(), aScopeObj
) {}
493 ~EventRunnable() = default;
495 bool PreDispatch(WorkerPrivate
* /* unused */) final
;
496 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
;
499 class SyncTeardownRunnable final
: public WorkerThreadProxySyncRunnable
{
501 SyncTeardownRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
)
502 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
) {}
505 ~SyncTeardownRunnable() = default;
507 virtual void RunOnMainThread(ErrorResult
& aRv
) override
{
508 mProxy
->Teardown(/* aSendUnpin */ true);
509 MOZ_ASSERT(!mProxy
->mSyncLoopTarget
);
513 class SetBackgroundRequestRunnable final
514 : public WorkerThreadProxySyncRunnable
{
518 SetBackgroundRequestRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
,
520 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
), mValue(aValue
) {}
523 ~SetBackgroundRequestRunnable() = default;
525 virtual void RunOnMainThread(ErrorResult
& aRv
) override
{
526 // XXXedgar, do we intend to ignore the errors?
527 mProxy
->mXHR
->SetMozBackgroundRequest(mValue
, aRv
);
531 class SetWithCredentialsRunnable final
: public WorkerThreadProxySyncRunnable
{
535 SetWithCredentialsRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
,
537 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
), mValue(aValue
) {}
540 ~SetWithCredentialsRunnable() = default;
542 virtual void RunOnMainThread(ErrorResult
& aRv
) override
{
543 mProxy
->mXHR
->SetWithCredentials(mValue
, aRv
);
547 class SetResponseTypeRunnable final
: public WorkerThreadProxySyncRunnable
{
548 XMLHttpRequestResponseType mResponseType
;
551 SetResponseTypeRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
,
552 XMLHttpRequestResponseType aResponseType
)
553 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
),
554 mResponseType(aResponseType
) {}
556 XMLHttpRequestResponseType
ResponseType() { return mResponseType
; }
559 ~SetResponseTypeRunnable() = default;
561 virtual void RunOnMainThread(ErrorResult
& aRv
) override
{
562 mProxy
->mXHR
->SetResponseTypeRaw(mResponseType
);
563 mResponseType
= mProxy
->mXHR
->ResponseType();
567 class SetTimeoutRunnable final
: public WorkerThreadProxySyncRunnable
{
571 SetTimeoutRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
,
573 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
),
574 mTimeout(aTimeout
) {}
577 ~SetTimeoutRunnable() = default;
579 virtual void RunOnMainThread(ErrorResult
& aRv
) override
{
580 mProxy
->mXHR
->SetTimeout(mTimeout
, aRv
);
584 class AbortRunnable final
: public WorkerThreadProxySyncRunnable
{
586 AbortRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
)
587 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
) {}
590 ~AbortRunnable() = default;
592 virtual void RunOnMainThread(ErrorResult
& aRv
) override
;
595 class GetAllResponseHeadersRunnable final
596 : public WorkerThreadProxySyncRunnable
{
597 nsCString
& mResponseHeaders
;
600 GetAllResponseHeadersRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
,
601 nsCString
& aResponseHeaders
)
602 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
),
603 mResponseHeaders(aResponseHeaders
) {}
606 ~GetAllResponseHeadersRunnable() = default;
608 virtual void RunOnMainThread(ErrorResult
& aRv
) override
{
609 mProxy
->mXHR
->GetAllResponseHeaders(mResponseHeaders
, aRv
);
613 class GetResponseHeaderRunnable final
: public WorkerThreadProxySyncRunnable
{
614 const nsCString mHeader
;
618 GetResponseHeaderRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
,
619 const nsACString
& aHeader
, nsCString
& aValue
)
620 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
),
625 ~GetResponseHeaderRunnable() = default;
627 virtual void RunOnMainThread(ErrorResult
& aRv
) override
{
628 mProxy
->mXHR
->GetResponseHeader(mHeader
, mValue
, aRv
);
632 class OpenRunnable final
: public WorkerThreadProxySyncRunnable
{
635 Optional
<nsAString
> mUser
;
637 Optional
<nsAString
> mPassword
;
638 nsString mPasswordStr
;
639 bool mBackgroundRequest
;
640 bool mWithCredentials
;
642 XMLHttpRequestResponseType mResponseType
;
643 const nsString mMimeTypeOverride
;
645 // Remember the worker thread's stack when the XHR was opened, so that it can
646 // be passed on to the net monitor.
647 UniquePtr
<SerializedStackHolder
> mOriginStack
;
649 // Remember the worker thread's stack when the XHR was opened for profiling
651 UniquePtr
<ProfileChunkedBuffer
> mSource
;
654 OpenRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
,
655 const nsACString
& aMethod
, const nsAString
& aURL
,
656 const Optional
<nsAString
>& aUser
,
657 const Optional
<nsAString
>& aPassword
, bool aBackgroundRequest
,
658 bool aWithCredentials
, uint32_t aTimeout
,
659 XMLHttpRequestResponseType aResponseType
,
660 const nsString
& aMimeTypeOverride
,
661 UniquePtr
<SerializedStackHolder
> aOriginStack
,
662 UniquePtr
<ProfileChunkedBuffer
> aSource
= nullptr)
663 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
),
666 mBackgroundRequest(aBackgroundRequest
),
667 mWithCredentials(aWithCredentials
),
669 mResponseType(aResponseType
),
670 mMimeTypeOverride(aMimeTypeOverride
),
671 mOriginStack(std::move(aOriginStack
)),
672 mSource(std::move(aSource
)) {
673 if (aUser
.WasPassed()) {
674 mUserStr
= aUser
.Value();
677 if (aPassword
.WasPassed()) {
678 mPasswordStr
= aPassword
.Value();
679 mPassword
= &mPasswordStr
;
684 ~OpenRunnable() = default;
686 virtual void RunOnMainThread(ErrorResult
& aRv
) override
{
687 WorkerPrivate
* oldWorker
= mProxy
->mWorkerPrivate
;
688 mProxy
->mWorkerPrivate
= mWorkerPrivate
;
690 MainThreadRunInternal(aRv
);
692 mProxy
->mWorkerPrivate
= oldWorker
;
695 void MainThreadRunInternal(ErrorResult
& aRv
);
698 class SetRequestHeaderRunnable final
: public WorkerThreadProxySyncRunnable
{
703 SetRequestHeaderRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
,
704 const nsACString
& aHeader
, const nsACString
& aValue
)
705 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
),
710 ~SetRequestHeaderRunnable() = default;
712 virtual void RunOnMainThread(ErrorResult
& aRv
) override
{
713 mProxy
->mXHR
->SetRequestHeader(mHeader
, mValue
, aRv
);
717 class OverrideMimeTypeRunnable final
: public WorkerThreadProxySyncRunnable
{
721 OverrideMimeTypeRunnable(WorkerPrivate
* aWorkerPrivate
, Proxy
* aProxy
,
722 const nsAString
& aMimeType
)
723 : WorkerThreadProxySyncRunnable(aWorkerPrivate
, aProxy
),
724 mMimeType(aMimeType
) {}
727 ~OverrideMimeTypeRunnable() = default;
729 virtual void RunOnMainThread(ErrorResult
& aRv
) override
{
730 mProxy
->mXHR
->OverrideMimeType(mMimeType
, aRv
);
735 XMLHttpRequestWorker
* mXMLHttpRequestPrivate
;
738 explicit AutoUnpinXHR(XMLHttpRequestWorker
* aXMLHttpRequestPrivate
)
739 : mXMLHttpRequestPrivate(aXMLHttpRequestPrivate
) {
740 MOZ_ASSERT(aXMLHttpRequestPrivate
);
744 if (mXMLHttpRequestPrivate
) {
745 mXMLHttpRequestPrivate
->Unpin();
749 void Clear() { mXMLHttpRequestPrivate
= nullptr; }
755 AssertIsOnMainThread();
756 MOZ_ASSERT(mWorkerPrivate
);
762 nsPIDOMWindowInner
* ownerWindow
= mWorkerPrivate
->GetWindow();
763 if (ownerWindow
&& !ownerWindow
->IsCurrentInnerWindow()) {
764 NS_WARNING("Window has navigated, cannot create XHR here.");
768 mXHR
= new XMLHttpRequestMainThread(ownerWindow
? ownerWindow
->AsGlobal()
770 mXHR
->Construct(mWorkerPrivate
->GetPrincipal(),
771 mWorkerPrivate
->CookieJarSettings(), true,
772 mWorkerPrivate
->GetBaseURI(), mWorkerPrivate
->GetLoadGroup(),
773 mWorkerPrivate
->GetPerformanceStorage(),
774 mWorkerPrivate
->CSPEventListener());
776 mXHR
->SetParameters(mMozAnon
, mMozSystem
);
777 mXHR
->SetClientInfoAndController(mClientInfo
, mController
);
780 mXHRUpload
= mXHR
->GetUpload(rv
);
781 if (NS_WARN_IF(rv
.Failed())) {
786 if (!AddRemoveEventListeners(false, true)) {
788 mXHRUpload
= nullptr;
795 void Proxy::Teardown(bool aSendUnpin
) {
796 AssertIsOnMainThread();
801 // NB: We are intentionally dropping events coming from xhr.abort on the
803 AddRemoveEventListeners(false, false);
807 if (NS_WARN_IF(rv
.Failed())) {
808 rv
.SuppressException();
811 if (mOutstandingSendCount
) {
813 RefPtr
<XHRUnpinRunnable
> runnable
=
814 new XHRUnpinRunnable(mWorkerPrivate
, mXMLHttpRequestPrivate
);
815 MOZ_ALWAYS_TRUE(runnable
->Dispatch());
818 if (mSyncLoopTarget
) {
819 // We have an unclosed sync loop. Fix that now.
820 RefPtr
<MainThreadStopSyncLoopRunnable
> runnable
=
821 new MainThreadStopSyncLoopRunnable(
822 mWorkerPrivate
, std::move(mSyncLoopTarget
), NS_ERROR_FAILURE
);
823 MOZ_ALWAYS_TRUE(runnable
->Dispatch());
826 mOutstandingSendCount
= 0;
829 mWorkerPrivate
= nullptr;
830 mXHRUpload
= nullptr;
834 MOZ_ASSERT(!mWorkerPrivate
);
835 MOZ_ASSERT(!mSyncLoopTarget
);
836 // If there are rare edge cases left that violate our invariants
837 // just ensure that they won't harm us too much.
838 mWorkerPrivate
= nullptr;
839 mSyncLoopTarget
= nullptr;
842 bool Proxy::AddRemoveEventListeners(bool aUpload
, bool aAdd
) {
843 AssertIsOnMainThread();
845 NS_ASSERTION(!aUpload
|| (mUploadEventListenersAttached
&& !aAdd
) ||
846 (!mUploadEventListenersAttached
&& aAdd
),
847 "Messed up logic for upload listeners!");
849 RefPtr
<DOMEventTargetHelper
> targetHelper
=
850 aUpload
? static_cast<XMLHttpRequestUpload
*>(mXHRUpload
.get())
851 : static_cast<XMLHttpRequestEventTarget
*>(mXHR
.get());
852 MOZ_ASSERT(targetHelper
, "This should never fail!");
854 uint32_t lastEventType
= aUpload
? STRING_LAST_EVENTTARGET
: STRING_LAST_XHR
;
856 nsAutoString eventType
;
857 for (uint32_t index
= 0; index
<= lastEventType
; index
++) {
858 eventType
= NS_ConvertASCIItoUTF16(sEventStrings
[index
]);
860 if (NS_FAILED(targetHelper
->AddEventListener(eventType
, this, false))) {
864 targetHelper
->RemoveEventListener(eventType
, this, false);
869 mUploadEventListenersAttached
= aAdd
;
875 NS_IMPL_ISUPPORTS(Proxy
, nsIDOMEventListener
)
878 Proxy::HandleEvent(Event
* aEvent
) {
879 AssertIsOnMainThread();
881 if (!mWorkerPrivate
|| !mXMLHttpRequestPrivate
) {
882 NS_ERROR("Shouldn't get here!");
887 aEvent
->GetType(type
);
889 bool isUploadTarget
= mXHR
!= aEvent
->GetTarget();
890 ProgressEvent
* progressEvent
= aEvent
->AsProgressEvent();
892 if (mInOpen
&& type
.EqualsASCII(sEventStrings
[STRING_readystatechange
])) {
893 if (mXHR
->ReadyState() == 1) {
894 mInnerEventStreamId
++;
900 JSObject
* junkScope
= xpc::UnprivilegedJunkScope(fallible
);
901 if (!junkScope
|| !jsapi
.Init(junkScope
)) {
902 return NS_ERROR_FAILURE
;
904 JSContext
* cx
= jsapi
.cx();
906 JS::Rooted
<JS::Value
> value(cx
);
907 if (!GetOrCreateDOMReflectorNoWrap(cx
, mXHR
, &value
)) {
908 return NS_ERROR_FAILURE
;
911 JS::Rooted
<JSObject
*> scope(cx
, &value
.toObject());
913 RefPtr
<EventRunnable
> runnable
;
915 if (!mIsSyncXHR
|| !type
.EqualsASCII(sEventStrings
[STRING_progress
])) {
916 runnable
= new EventRunnable(
917 this, isUploadTarget
, type
, progressEvent
->LengthComputable(),
918 progressEvent
->Loaded(), progressEvent
->Total(), scope
);
921 runnable
= new EventRunnable(this, isUploadTarget
, type
, scope
);
925 runnable
->Dispatch();
929 if (!isUploadTarget
) {
930 if (type
.EqualsASCII(sEventStrings
[STRING_loadstart
])) {
931 mMainThreadSeenLoadStart
= true;
932 } else if (mMainThreadSeenLoadStart
&&
933 type
.EqualsASCII(sEventStrings
[STRING_loadend
])) {
934 mMainThreadSeenLoadStart
= false;
936 RefPtr
<LoadStartDetectionRunnable
> runnable
=
937 new LoadStartDetectionRunnable(this, mXMLHttpRequestPrivate
);
938 if (!runnable
->RegisterAndDispatch()) {
939 NS_WARNING("Failed to dispatch LoadStartDetectionRunnable!");
947 NS_IMPL_ISUPPORTS_INHERITED(LoadStartDetectionRunnable
, Runnable
,
951 LoadStartDetectionRunnable::Run() {
952 AssertIsOnMainThread();
954 mXHR
->RemoveEventListener(mEventType
, this, false);
956 if (!mReceivedLoadStart
) {
957 if (mProxy
->mOutstandingSendCount
> 1) {
958 mProxy
->mOutstandingSendCount
--;
959 } else if (mProxy
->mOutstandingSendCount
== 1) {
962 RefPtr
<ProxyCompleteRunnable
> runnable
= new ProxyCompleteRunnable(
963 mWorkerPrivate
, mProxy
, mXMLHttpRequestPrivate
, mChannelId
);
964 if (runnable
->Dispatch()) {
965 mProxy
->mWorkerPrivate
= nullptr;
966 mProxy
->mSyncLoopTarget
= nullptr;
967 mProxy
->mOutstandingSendCount
--;
974 mXMLHttpRequestPrivate
= nullptr;
979 LoadStartDetectionRunnable::HandleEvent(Event
* aEvent
) {
980 AssertIsOnMainThread();
985 aEvent
->GetType(type
);
986 MOZ_ASSERT(type
== mEventType
);
990 mReceivedLoadStart
= true;
994 bool EventRunnable::PreDispatch(WorkerPrivate
* /* unused */) {
995 AssertIsOnMainThread();
998 DebugOnly
<bool> ok
= jsapi
.Init(xpc::NativeGlobal(mScopeObj
));
1000 JSContext
* cx
= jsapi
.cx();
1001 // Now keep the mScopeObj alive for the duration
1002 JS::Rooted
<JSObject
*> scopeObj(cx
, mScopeObj
);
1003 // And reset mScopeObj now, before we have a chance to run its destructor on
1004 // some background thread.
1007 RefPtr
<XMLHttpRequestMainThread
>& xhr
= mProxy
->mXHR
;
1012 XMLHttpRequestResponseType type
= xhr
->ResponseType();
1014 // We want to take the result data only if this is available.
1015 if (mType
.EqualsASCII(sEventStrings
[STRING_readystatechange
])) {
1017 case XMLHttpRequestResponseType::_empty
:
1018 case XMLHttpRequestResponseType::Text
: {
1019 xhr
->GetResponseText(mResponseData
->mResponseText
, rv
);
1020 mResponseData
->mResponseResult
= rv
.StealNSResult();
1024 case XMLHttpRequestResponseType::Blob
: {
1025 mResponseData
->mResponseBlobImpl
= xhr
->GetResponseBlobImpl();
1029 case XMLHttpRequestResponseType::Arraybuffer
: {
1030 mResponseData
->mResponseArrayBufferBuilder
=
1031 xhr
->GetResponseArrayBufferBuilder();
1035 case XMLHttpRequestResponseType::Json
: {
1036 mResponseData
->mResponseResult
=
1037 xhr
->GetResponseTextForJSON(mResponseData
->mResponseJSON
);
1042 MOZ_ASSERT_UNREACHABLE("Invalid response type");
1047 mStatus
= xhr
->GetStatus(rv
);
1048 mStatusResult
= rv
.StealNSResult();
1050 xhr
->GetStatusText(mStatusText
, rv
);
1051 MOZ_ASSERT(!rv
.Failed());
1053 mReadyState
= xhr
->ReadyState();
1055 xhr
->GetResponseURL(mResponseURL
);
1060 bool EventRunnable::WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) {
1061 if (mEventStreamId
!= mProxy
->mOuterEventStreamId
) {
1062 // Threads raced, this event is now obsolete.
1066 if (!mProxy
->mXMLHttpRequestPrivate
) {
1067 // Object was finalized, bail.
1071 if (mType
.EqualsASCII(sEventStrings
[STRING_loadstart
])) {
1073 mProxy
->mSeenUploadLoadStart
= true;
1075 mProxy
->mSeenLoadStart
= true;
1077 } else if (mType
.EqualsASCII(sEventStrings
[STRING_loadend
])) {
1079 mProxy
->mSeenUploadLoadStart
= false;
1080 if (mProxy
->mDispatchPrematureAbortEventToUpload
) {
1081 // We've already dispatched premature abort events.
1085 mProxy
->mSeenLoadStart
= false;
1086 if (mProxy
->mDispatchPrematureAbortEvent
) {
1087 // We've already dispatched premature abort events.
1091 } else if (mType
.EqualsASCII(sEventStrings
[STRING_abort
])) {
1092 if ((mUploadEvent
&& mProxy
->mDispatchPrematureAbortEventToUpload
) ||
1093 (!mUploadEvent
&& mProxy
->mDispatchPrematureAbortEvent
)) {
1094 // We've already dispatched premature abort events.
1099 if (mProgressEvent
) {
1100 // Cache these for premature abort events.
1102 mProxy
->mLastUploadLengthComputable
= mLengthComputable
;
1103 mProxy
->mLastUploadLoaded
= mLoaded
;
1104 mProxy
->mLastUploadTotal
= mTotal
;
1106 mProxy
->mLastLengthComputable
= mLengthComputable
;
1107 mProxy
->mLastLoaded
= mLoaded
;
1108 mProxy
->mLastTotal
= mTotal
;
1112 UniquePtr
<XMLHttpRequestWorker::StateData
> state(
1113 new XMLHttpRequestWorker::StateData());
1115 state
->mStatusResult
= mStatusResult
;
1116 state
->mStatus
= mStatus
;
1118 state
->mStatusText
= mStatusText
;
1120 state
->mReadyState
= mReadyState
;
1122 state
->mResponseURL
= mResponseURL
;
1124 XMLHttpRequestWorker
* xhr
= mProxy
->mXMLHttpRequestPrivate
;
1125 xhr
->UpdateState(std::move(state
),
1126 mType
.EqualsASCII(sEventStrings
[STRING_readystatechange
])
1127 ? std::move(mResponseData
)
1130 if (mUploadEvent
&& !xhr
->GetUploadObjectNoCreate()) {
1134 XMLHttpRequestEventTarget
* target
;
1136 target
= xhr
->GetUploadObjectNoCreate();
1143 RefPtr
<Event
> event
;
1144 if (mProgressEvent
) {
1145 ProgressEventInit init
;
1146 init
.mBubbles
= false;
1147 init
.mCancelable
= false;
1148 init
.mLengthComputable
= mLengthComputable
;
1149 init
.mLoaded
= mLoaded
;
1150 init
.mTotal
= mTotal
;
1152 event
= ProgressEvent::Constructor(target
, mType
, init
);
1154 event
= NS_NewDOMEvent(target
, nullptr, nullptr);
1157 event
->InitEvent(mType
, false, false);
1165 event
->SetTrusted(true);
1167 target
->DispatchEvent(*event
);
1172 bool WorkerThreadProxySyncRunnable::MainThreadRun() {
1173 AssertIsOnMainThread();
1175 nsCOMPtr
<nsIEventTarget
> tempTarget
= mSyncLoopTarget
;
1177 mProxy
->mSyncEventResponseTarget
.swap(tempTarget
);
1180 RunOnMainThread(rv
);
1181 mErrorCode
= rv
.StealNSResult();
1183 mProxy
->mSyncEventResponseTarget
.swap(tempTarget
);
1188 void AbortRunnable::RunOnMainThread(ErrorResult
& aRv
) {
1189 mProxy
->mInnerEventStreamId
++;
1191 WorkerPrivate
* oldWorker
= mProxy
->mWorkerPrivate
;
1192 mProxy
->mWorkerPrivate
= mWorkerPrivate
;
1194 mProxy
->mXHR
->Abort(aRv
);
1196 mProxy
->mWorkerPrivate
= oldWorker
;
1201 void OpenRunnable::MainThreadRunInternal(ErrorResult
& aRv
) {
1202 if (!mProxy
->Init()) {
1203 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1207 if (mBackgroundRequest
) {
1208 mProxy
->mXHR
->SetMozBackgroundRequestExternal(mBackgroundRequest
, aRv
);
1215 mProxy
->mXHR
->SetOriginStack(std::move(mOriginStack
));
1218 if (mWithCredentials
) {
1219 mProxy
->mXHR
->SetWithCredentials(mWithCredentials
, aRv
);
1220 if (NS_WARN_IF(aRv
.Failed())) {
1226 mProxy
->mXHR
->SetTimeout(mTimeout
, aRv
);
1227 if (NS_WARN_IF(aRv
.Failed())) {
1232 if (!mMimeTypeOverride
.IsVoid()) {
1233 mProxy
->mXHR
->OverrideMimeType(mMimeTypeOverride
, aRv
);
1234 if (NS_WARN_IF(aRv
.Failed())) {
1239 MOZ_ASSERT(!mProxy
->mInOpen
);
1240 mProxy
->mInOpen
= true;
1243 mMethod
, mURL
, true, mUser
.WasPassed() ? mUser
.Value() : VoidString(),
1244 mPassword
.WasPassed() ? mPassword
.Value() : VoidString(), aRv
);
1246 MOZ_ASSERT(mProxy
->mInOpen
);
1247 mProxy
->mInOpen
= false;
1249 if (NS_WARN_IF(aRv
.Failed())) {
1254 mProxy
->mXHR
->SetSource(std::move(mSource
));
1257 mProxy
->mXHR
->SetResponseType(mResponseType
, aRv
);
1260 void SendRunnable::RunOnMainThread(ErrorResult
& aRv
) {
1261 // Before we change any state let's check if we can send.
1262 if (!mProxy
->mXHR
->CanSend(aRv
)) {
1267 DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString
>
1273 JS::Rooted
<JSObject
*> globalObject(RootingCx(),
1274 xpc::UnprivilegedJunkScope(fallible
));
1275 if (NS_WARN_IF(!globalObject
)) {
1276 aRv
.Throw(NS_ERROR_FAILURE
);
1280 nsCOMPtr
<nsIGlobalObject
> parent
= xpc::NativeGlobal(globalObject
);
1281 if (NS_WARN_IF(!parent
)) {
1282 aRv
.Throw(NS_ERROR_FAILURE
);
1286 RefPtr
<Blob
> blob
= Blob::Create(parent
, mBlobImpl
);
1289 DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString
&
1290 ref
= payload
.SetValue();
1291 ref
.SetAsBlob() = blob
;
1294 // Send() has been already called, reset the proxy.
1295 if (mProxy
->mWorkerPrivate
) {
1299 mProxy
->mWorkerPrivate
= mWorkerPrivate
;
1301 MOZ_ASSERT(!mProxy
->mSyncLoopTarget
);
1302 mProxy
->mSyncLoopTarget
.swap(mSyncLoopTarget
);
1304 if (mHasUploadListeners
) {
1305 // Send() can be called more than once before failure,
1306 // so don't attach the upload listeners more than once.
1307 if (!mProxy
->mUploadEventListenersAttached
&&
1308 !mProxy
->AddRemoveEventListeners(true, true)) {
1309 MOZ_ASSERT(false, "This should never fail!");
1313 mProxy
->mInnerChannelId
++;
1315 mProxy
->mXHR
->Send(payload
, aRv
);
1317 if (!aRv
.Failed()) {
1318 mProxy
->mOutstandingSendCount
++;
1320 if (!mHasUploadListeners
) {
1321 // Send() can be called more than once before failure,
1322 // so don't attach the upload listeners more than once.
1323 if (!mProxy
->mUploadEventListenersAttached
&&
1324 !mProxy
->AddRemoveEventListeners(true, true)) {
1325 MOZ_ASSERT(false, "This should never fail!");
1329 // In case of failure we just break the sync loop
1330 mProxy
->mSyncLoopTarget
= nullptr;
1331 mSyncLoopTarget
= nullptr;
1335 XMLHttpRequestWorker::XMLHttpRequestWorker(WorkerPrivate
* aWorkerPrivate
,
1336 nsIGlobalObject
* aGlobalObject
)
1337 : XMLHttpRequest(aGlobalObject
),
1338 mWorkerPrivate(aWorkerPrivate
),
1339 mResponseType(XMLHttpRequestResponseType::_empty
),
1340 mStateData(new StateData()),
1341 mResponseData(new ResponseData()),
1342 mResponseArrayBufferValue(nullptr),
1343 mResponseJSONValue(JS::UndefinedValue()),
1345 mBackgroundRequest(false),
1346 mWithCredentials(false),
1348 mFlagSendActive(false),
1351 mMimeTypeOverride(VoidString()) {
1352 mWorkerPrivate
->AssertIsOnWorkerThread();
1354 mozilla::HoldJSObjects(this);
1357 XMLHttpRequestWorker::~XMLHttpRequestWorker() {
1358 mWorkerPrivate
->AssertIsOnWorkerThread();
1360 ReleaseProxy(XHRIsGoingAway
);
1362 MOZ_ASSERT(!mWorkerRef
);
1364 mozilla::DropJSObjects(this);
1367 NS_IMPL_ADDREF_INHERITED(XMLHttpRequestWorker
, XMLHttpRequestEventTarget
)
1368 NS_IMPL_RELEASE_INHERITED(XMLHttpRequestWorker
, XMLHttpRequestEventTarget
)
1370 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XMLHttpRequestWorker
)
1371 NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget
)
1373 NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestWorker
)
1375 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestWorker
,
1376 XMLHttpRequestEventTarget
)
1377 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload
)
1378 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseBlob
)
1379 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1381 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestWorker
,
1382 XMLHttpRequestEventTarget
)
1383 tmp
->ReleaseProxy(XHRIsGoingAway
);
1384 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload
)
1385 tmp
->mResponseData
= nullptr;
1386 tmp
->mResponseBlob
= nullptr;
1387 tmp
->mResponseArrayBufferValue
= nullptr;
1388 tmp
->mResponseJSONValue
.setUndefined();
1389 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1391 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestWorker
,
1392 XMLHttpRequestEventTarget
)
1393 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResponseArrayBufferValue
)
1394 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResponseJSONValue
)
1395 NS_IMPL_CYCLE_COLLECTION_TRACE_END
1398 already_AddRefed
<XMLHttpRequest
> XMLHttpRequestWorker::Construct(
1399 const GlobalObject
& aGlobal
, const MozXMLHttpRequestParameters
& aParams
,
1401 JSContext
* cx
= aGlobal
.Context();
1402 WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(cx
);
1403 MOZ_ASSERT(workerPrivate
);
1405 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
1406 if (NS_WARN_IF(!global
)) {
1407 aRv
.Throw(NS_ERROR_FAILURE
);
1411 RefPtr
<XMLHttpRequestWorker
> xhr
=
1412 new XMLHttpRequestWorker(workerPrivate
, global
);
1414 if (workerPrivate
->XHRParamsAllowed()) {
1415 if (aParams
.mMozSystem
)
1416 xhr
->mMozAnon
= true;
1418 xhr
->mMozAnon
= aParams
.mMozAnon
;
1419 xhr
->mMozSystem
= aParams
.mMozSystem
;
1422 return xhr
.forget();
1425 void XMLHttpRequestWorker::ReleaseProxy(ReleaseType aType
) {
1426 // Can't assert that we're on the worker thread here because mWorkerPrivate
1430 if (aType
== XHRIsGoingAway
) {
1431 // We're in a GC finalizer, so we can't do a sync call here (and we don't
1433 RefPtr
<AsyncTeardownRunnable
> runnable
=
1434 new AsyncTeardownRunnable(mProxy
);
1437 if (NS_FAILED(mWorkerPrivate
->DispatchToMainThread(runnable
.forget()))) {
1438 NS_ERROR("Failed to dispatch teardown runnable!");
1441 // This isn't necessary if the worker is going away or the XHR is going
1443 if (aType
== Default
) {
1444 // Don't let any more events run.
1445 mProxy
->mOuterEventStreamId
++;
1448 // We need to make a sync call here.
1449 RefPtr
<SyncTeardownRunnable
> runnable
=
1450 new SyncTeardownRunnable(mWorkerPrivate
, mProxy
);
1453 IgnoredErrorResult forAssertionsOnly
;
1454 // This runnable _must_ be executed.
1455 runnable
->Dispatch(Dead
, forAssertionsOnly
);
1456 MOZ_DIAGNOSTIC_ASSERT(!forAssertionsOnly
.Failed());
1461 void XMLHttpRequestWorker::MaybePin(ErrorResult
& aRv
) {
1462 mWorkerPrivate
->AssertIsOnWorkerThread();
1468 RefPtr
<XMLHttpRequestWorker
> self
= this;
1470 StrongWorkerRef::Create(mWorkerPrivate
, "XMLHttpRequestWorker", [self
]() {
1471 if (!self
->mCanceled
) {
1472 self
->mCanceled
= true;
1473 self
->ReleaseProxy(WorkerIsGoingAway
);
1476 if (NS_WARN_IF(!mWorkerRef
)) {
1477 aRv
.Throw(NS_ERROR_FAILURE
);
1484 void XMLHttpRequestWorker::MaybeDispatchPrematureAbortEvents(ErrorResult
& aRv
) {
1485 mWorkerPrivate
->AssertIsOnWorkerThread();
1488 // Only send readystatechange event when state changed.
1489 bool isStateChanged
= false;
1490 if ((mStateData
->mReadyState
== 1 && mStateData
->mFlagSend
) ||
1491 mStateData
->mReadyState
== 2 || mStateData
->mReadyState
== 3) {
1492 isStateChanged
= true;
1493 mStateData
->mReadyState
= 4;
1496 if (mProxy
->mSeenUploadLoadStart
) {
1497 MOZ_ASSERT(mUpload
);
1499 DispatchPrematureAbortEvent(mUpload
, u
"abort"_ns
, true, aRv
);
1504 DispatchPrematureAbortEvent(mUpload
, u
"loadend"_ns
, true, aRv
);
1509 // Similarly to null check in ::Open, mProxy may have been cleared here.
1511 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1515 mProxy
->mSeenUploadLoadStart
= false;
1516 mProxy
->mDispatchPrematureAbortEventToUpload
= true;
1519 if (mProxy
->mSeenLoadStart
) {
1520 if (isStateChanged
) {
1521 DispatchPrematureAbortEvent(this, u
"readystatechange"_ns
, false, aRv
);
1527 DispatchPrematureAbortEvent(this, u
"abort"_ns
, false, aRv
);
1532 DispatchPrematureAbortEvent(this, u
"loadend"_ns
, false, aRv
);
1537 // Similarly to null check in ::Open, mProxy may have been cleared here.
1539 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1543 mProxy
->mSeenLoadStart
= false;
1544 mProxy
->mDispatchPrematureAbortEvent
= true;
1548 void XMLHttpRequestWorker::DispatchPrematureAbortEvent(
1549 EventTarget
* aTarget
, const nsAString
& aEventType
, bool aUploadTarget
,
1551 mWorkerPrivate
->AssertIsOnWorkerThread();
1552 MOZ_ASSERT(aTarget
);
1555 aRv
.Throw(NS_ERROR_FAILURE
);
1559 RefPtr
<Event
> event
;
1560 if (aEventType
.EqualsLiteral("readystatechange")) {
1561 event
= NS_NewDOMEvent(aTarget
, nullptr, nullptr);
1562 event
->InitEvent(aEventType
, false, false);
1564 if (mProxy
->mIsSyncXHR
&&
1565 aEventType
.EqualsASCII(sEventStrings
[STRING_progress
])) {
1569 ProgressEventInit init
;
1570 init
.mBubbles
= false;
1571 init
.mCancelable
= false;
1572 if (aUploadTarget
) {
1573 init
.mLengthComputable
= mProxy
->mLastUploadLengthComputable
;
1574 init
.mLoaded
= mProxy
->mLastUploadLoaded
;
1575 init
.mTotal
= mProxy
->mLastUploadTotal
;
1577 init
.mLengthComputable
= mProxy
->mLastLengthComputable
;
1578 init
.mLoaded
= mProxy
->mLastLoaded
;
1579 init
.mTotal
= mProxy
->mLastTotal
;
1581 event
= ProgressEvent::Constructor(aTarget
, aEventType
, init
);
1585 aRv
.Throw(NS_ERROR_FAILURE
);
1589 event
->SetTrusted(true);
1591 aTarget
->DispatchEvent(*event
);
1594 void XMLHttpRequestWorker::Unpin() {
1595 mWorkerPrivate
->AssertIsOnWorkerThread();
1597 MOZ_ASSERT(mWorkerRef
, "Mismatched calls to Unpin!");
1598 mWorkerRef
= nullptr;
1603 void XMLHttpRequestWorker::SendInternal(const BodyExtractorBase
* aBody
,
1605 mWorkerPrivate
->AssertIsOnWorkerThread();
1607 // We don't really need to keep the same body-type when we proxy the send()
1608 // call to the main-thread XHR. Let's extract the nsIInputStream from the
1609 // aBody and let's wrap it into a StreamBlobImpl.
1611 RefPtr
<BlobImpl
> blobImpl
;
1614 nsAutoCString charset
;
1615 nsAutoCString defaultContentType
;
1616 nsCOMPtr
<nsIInputStream
> uploadStream
;
1619 aRv
= aBody
->GetAsStream(getter_AddRefs(uploadStream
), &size_u64
,
1620 defaultContentType
, charset
);
1621 if (NS_WARN_IF(aRv
.Failed())) {
1625 blobImpl
= StreamBlobImpl::Create(uploadStream
.forget(),
1626 NS_ConvertUTF8toUTF16(defaultContentType
),
1627 size_u64
, u
"StreamBlobImpl"_ns
);
1628 MOZ_ASSERT(blobImpl
);
1631 RefPtr
<SendRunnable
> sendRunnable
=
1632 new SendRunnable(mWorkerPrivate
, mProxy
, blobImpl
);
1634 // No send() calls when open is running.
1635 if (mProxy
->mOpenCount
) {
1636 aRv
.Throw(NS_ERROR_FAILURE
);
1640 bool hasUploadListeners
= mUpload
? mUpload
->HasListeners() : false;
1647 AutoUnpinXHR
autoUnpin(this);
1648 Maybe
<AutoSyncLoopHolder
> autoSyncLoop
;
1650 nsCOMPtr
<nsISerialEventTarget
> syncLoopTarget
;
1651 bool isSyncXHR
= mProxy
->mIsSyncXHR
;
1653 autoSyncLoop
.emplace(mWorkerPrivate
, Canceling
);
1654 syncLoopTarget
= autoSyncLoop
->GetSerialEventTarget();
1655 if (!syncLoopTarget
) {
1656 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1661 mProxy
->mOuterChannelId
++;
1662 mProxy
->mDispatchPrematureAbortEvent
= false;
1663 mProxy
->mDispatchPrematureAbortEventToUpload
= false;
1665 sendRunnable
->SetSyncLoopTarget(syncLoopTarget
);
1666 sendRunnable
->SetHaveUploadListeners(hasUploadListeners
);
1668 mStateData
->mFlagSend
= true;
1670 sendRunnable
->Dispatch(Canceling
, aRv
);
1672 // Dispatch() may have spun the event loop and we may have already unrooted.
1673 // If so we don't want autoUnpin to try again.
1682 MOZ_ASSERT(!autoSyncLoop
);
1688 bool succeeded
= NS_SUCCEEDED(autoSyncLoop
->Run());
1689 mStateData
->mFlagSend
= false;
1691 // Don't clobber an existing exception that we may have thrown on aRv
1692 // already... though can there really be one? In any case, it seems to me
1693 // that this autoSyncLoop->Run() can never fail, since the StopSyncLoop call
1694 // for it will come from ProxyCompleteRunnable and that always passes true for
1696 if (!succeeded
&& !aRv
.Failed()) {
1697 aRv
.Throw(NS_ERROR_FAILURE
);
1701 void XMLHttpRequestWorker::Open(const nsACString
& aMethod
,
1702 const nsAString
& aUrl
, bool aAsync
,
1703 const Optional
<nsAString
>& aUser
,
1704 const Optional
<nsAString
>& aPassword
,
1706 mWorkerPrivate
->AssertIsOnWorkerThread();
1709 aRv
.ThrowUncatchableException();
1713 bool alsoOverrideMimeType
= false;
1715 MaybeDispatchPrematureAbortEvents(aRv
);
1720 Maybe
<ClientInfo
> clientInfo(
1721 mWorkerPrivate
->GlobalScope()->GetClientInfo());
1722 if (clientInfo
.isNothing()) {
1723 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1726 mProxy
= new Proxy(this, clientInfo
.ref(),
1727 mWorkerPrivate
->GlobalScope()->GetController(), mMozAnon
,
1729 alsoOverrideMimeType
= true;
1732 mProxy
->mOuterEventStreamId
++;
1734 UniquePtr
<SerializedStackHolder
> stack
;
1735 if (mWorkerPrivate
->IsWatchedByDevTools()) {
1736 if (JSContext
* cx
= nsContentUtils::GetCurrentJSContext()) {
1737 stack
= GetCurrentStackForNetMonitor(cx
);
1741 RefPtr
<OpenRunnable
> runnable
= new OpenRunnable(
1742 mWorkerPrivate
, mProxy
, aMethod
, aUrl
, aUser
, aPassword
,
1743 mBackgroundRequest
, mWithCredentials
, mTimeout
, mResponseType
,
1744 alsoOverrideMimeType
? mMimeTypeOverride
: VoidString(), std::move(stack
),
1745 profiler_capture_backtrace());
1747 ++mProxy
->mOpenCount
;
1748 runnable
->Dispatch(Canceling
, aRv
);
1750 if (mProxy
&& !--mProxy
->mOpenCount
) {
1757 // We have been released in one of the nested Open() calls.
1759 aRv
.Throw(NS_ERROR_FAILURE
);
1763 --mProxy
->mOpenCount
;
1764 mProxy
->mIsSyncXHR
= !aAsync
;
1767 void XMLHttpRequestWorker::SetRequestHeader(const nsACString
& aHeader
,
1768 const nsACString
& aValue
,
1770 mWorkerPrivate
->AssertIsOnWorkerThread();
1773 aRv
.ThrowUncatchableException();
1778 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1782 RefPtr
<SetRequestHeaderRunnable
> runnable
=
1783 new SetRequestHeaderRunnable(mWorkerPrivate
, mProxy
, aHeader
, aValue
);
1784 runnable
->Dispatch(Canceling
, aRv
);
1787 void XMLHttpRequestWorker::SetTimeout(uint32_t aTimeout
, ErrorResult
& aRv
) {
1788 mWorkerPrivate
->AssertIsOnWorkerThread();
1791 aRv
.ThrowUncatchableException();
1795 mTimeout
= aTimeout
;
1798 // Open may not have been called yet, in which case we'll handle the
1799 // timeout in OpenRunnable.
1803 RefPtr
<SetTimeoutRunnable
> runnable
=
1804 new SetTimeoutRunnable(mWorkerPrivate
, mProxy
, aTimeout
);
1805 runnable
->Dispatch(Canceling
, aRv
);
1808 void XMLHttpRequestWorker::SetWithCredentials(bool aWithCredentials
,
1810 mWorkerPrivate
->AssertIsOnWorkerThread();
1813 aRv
.ThrowUncatchableException();
1817 mWithCredentials
= aWithCredentials
;
1820 // Open may not have been called yet, in which case we'll handle the
1821 // credentials in OpenRunnable.
1825 RefPtr
<SetWithCredentialsRunnable
> runnable
=
1826 new SetWithCredentialsRunnable(mWorkerPrivate
, mProxy
, aWithCredentials
);
1827 runnable
->Dispatch(Canceling
, aRv
);
1830 void XMLHttpRequestWorker::SetMozBackgroundRequest(bool aBackgroundRequest
,
1832 mWorkerPrivate
->AssertIsOnWorkerThread();
1835 aRv
.ThrowUncatchableException();
1839 mBackgroundRequest
= aBackgroundRequest
;
1842 // Open may not have been called yet, in which case we'll handle the
1843 // background request in OpenRunnable.
1847 RefPtr
<SetBackgroundRequestRunnable
> runnable
=
1848 new SetBackgroundRequestRunnable(mWorkerPrivate
, mProxy
,
1849 aBackgroundRequest
);
1850 runnable
->Dispatch(Canceling
, aRv
);
1853 XMLHttpRequestUpload
* XMLHttpRequestWorker::GetUpload(ErrorResult
& aRv
) {
1854 mWorkerPrivate
->AssertIsOnWorkerThread();
1857 aRv
.ThrowUncatchableException();
1862 mUpload
= new XMLHttpRequestUpload(this);
1868 void XMLHttpRequestWorker::Send(
1870 DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString
>&
1873 mWorkerPrivate
->AssertIsOnWorkerThread();
1875 if (mFlagSendActive
) {
1876 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT
);
1879 mFlagSendActive
= true;
1880 auto clearRecursionFlag
= MakeScopeExit([&]() {
1881 // No one else should have touched this flag.
1882 MOZ_ASSERT(mFlagSendActive
);
1883 mFlagSendActive
= false;
1887 aRv
.ThrowUncatchableException();
1891 if (mStateData
->mReadyState
!= XMLHttpRequest_Binding::OPENED
) {
1892 aRv
.ThrowInvalidStateError("XMLHttpRequest state must be OPENED.");
1896 if (!mProxy
|| mStateData
->mFlagSend
) {
1897 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1901 if (aData
.IsNull()) {
1902 SendInternal(nullptr, aRv
);
1906 if (aData
.Value().IsDocument()) {
1907 MOZ_ASSERT_UNREACHABLE("Documents are not exposed to workers.");
1908 aRv
.Throw(NS_ERROR_FAILURE
);
1912 if (aData
.Value().IsBlob()) {
1913 BodyExtractor
<const Blob
> body(&aData
.Value().GetAsBlob());
1914 SendInternal(&body
, aRv
);
1918 if (aData
.Value().IsArrayBuffer()) {
1919 BodyExtractor
<const ArrayBuffer
> body(&aData
.Value().GetAsArrayBuffer());
1920 SendInternal(&body
, aRv
);
1924 if (aData
.Value().IsArrayBufferView()) {
1925 BodyExtractor
<const ArrayBufferView
> body(
1926 &aData
.Value().GetAsArrayBufferView());
1927 SendInternal(&body
, aRv
);
1931 if (aData
.Value().IsFormData()) {
1932 BodyExtractor
<const FormData
> body(&aData
.Value().GetAsFormData());
1933 SendInternal(&body
, aRv
);
1937 if (aData
.Value().IsURLSearchParams()) {
1938 BodyExtractor
<const URLSearchParams
> body(
1939 &aData
.Value().GetAsURLSearchParams());
1940 SendInternal(&body
, aRv
);
1944 if (aData
.Value().IsUSVString()) {
1945 BodyExtractor
<const nsAString
> body(&aData
.Value().GetAsUSVString());
1946 SendInternal(&body
, aRv
);
1951 void XMLHttpRequestWorker::Abort(ErrorResult
& aRv
) {
1952 mWorkerPrivate
->AssertIsOnWorkerThread();
1955 aRv
.ThrowUncatchableException();
1963 // Set our status to 0 and statusText to "" if we
1964 // will be aborting an ongoing fetch, so the upcoming
1965 // abort events we dispatch have the correct info.
1966 if ((mStateData
->mReadyState
== XMLHttpRequest_Binding::OPENED
&&
1967 mStateData
->mFlagSend
) ||
1968 mStateData
->mReadyState
== XMLHttpRequest_Binding::HEADERS_RECEIVED
||
1969 mStateData
->mReadyState
== XMLHttpRequest_Binding::LOADING
||
1970 mStateData
->mReadyState
== XMLHttpRequest_Binding::DONE
) {
1971 mStateData
->mStatus
= 0;
1972 mStateData
->mStatusText
.Truncate();
1975 MaybeDispatchPrematureAbortEvents(aRv
);
1980 if (mStateData
->mReadyState
== 4) {
1981 // No one did anything to us while we fired abort events, so reset our state
1983 mStateData
->mReadyState
= 0;
1986 mProxy
->mOuterEventStreamId
++;
1988 RefPtr
<AbortRunnable
> runnable
= new AbortRunnable(mWorkerPrivate
, mProxy
);
1989 runnable
->Dispatch(Canceling
, aRv
);
1992 void XMLHttpRequestWorker::GetResponseHeader(const nsACString
& aHeader
,
1993 nsACString
& aResponseHeader
,
1995 mWorkerPrivate
->AssertIsOnWorkerThread();
1998 aRv
.ThrowUncatchableException();
2003 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2007 nsCString responseHeader
;
2008 RefPtr
<GetResponseHeaderRunnable
> runnable
= new GetResponseHeaderRunnable(
2009 mWorkerPrivate
, mProxy
, aHeader
, responseHeader
);
2010 runnable
->Dispatch(Canceling
, aRv
);
2014 aResponseHeader
= responseHeader
;
2017 void XMLHttpRequestWorker::GetAllResponseHeaders(nsACString
& aResponseHeaders
,
2019 mWorkerPrivate
->AssertIsOnWorkerThread();
2022 aRv
.ThrowUncatchableException();
2027 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2031 nsCString responseHeaders
;
2032 RefPtr
<GetAllResponseHeadersRunnable
> runnable
=
2033 new GetAllResponseHeadersRunnable(mWorkerPrivate
, mProxy
,
2035 runnable
->Dispatch(Canceling
, aRv
);
2040 aResponseHeaders
= responseHeaders
;
2043 void XMLHttpRequestWorker::OverrideMimeType(const nsAString
& aMimeType
,
2045 mWorkerPrivate
->AssertIsOnWorkerThread();
2048 aRv
.ThrowUncatchableException();
2052 // We're supposed to throw if the state is LOADING or DONE.
2053 if (mStateData
->mReadyState
== XMLHttpRequest_Binding::LOADING
||
2054 mStateData
->mReadyState
== XMLHttpRequest_Binding::DONE
) {
2055 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2059 mMimeTypeOverride
= aMimeType
;
2062 RefPtr
<OverrideMimeTypeRunnable
> runnable
=
2063 new OverrideMimeTypeRunnable(mWorkerPrivate
, mProxy
, aMimeType
);
2064 runnable
->Dispatch(Canceling
, aRv
);
2068 void XMLHttpRequestWorker::SetResponseType(
2069 XMLHttpRequestResponseType aResponseType
, ErrorResult
& aRv
) {
2070 mWorkerPrivate
->AssertIsOnWorkerThread();
2072 // "document" is fine for the main thread but not for a worker. Short-circuit
2074 if (aResponseType
== XMLHttpRequestResponseType::Document
) {
2079 // Open() has not been called yet. We store the responseType and we will use
2080 // it later in Open().
2081 mResponseType
= aResponseType
;
2085 if (mStateData
->mReadyState
== XMLHttpRequest_Binding::LOADING
||
2086 mStateData
->mReadyState
== XMLHttpRequest_Binding::DONE
) {
2087 aRv
.ThrowInvalidStateError(
2088 "Cannot set 'responseType' property on XMLHttpRequest after 'send()' "
2089 "(when its state is LOADING or DONE).");
2093 RefPtr
<SetResponseTypeRunnable
> runnable
=
2094 new SetResponseTypeRunnable(mWorkerPrivate
, mProxy
, aResponseType
);
2095 runnable
->Dispatch(Canceling
, aRv
);
2100 mResponseType
= runnable
->ResponseType();
2103 void XMLHttpRequestWorker::GetResponse(JSContext
* aCx
,
2104 JS::MutableHandle
<JS::Value
> aResponse
,
2106 if (NS_FAILED(mResponseData
->mResponseResult
)) {
2107 aRv
.Throw(mResponseData
->mResponseResult
);
2111 switch (mResponseType
) {
2112 case XMLHttpRequestResponseType::_empty
:
2113 case XMLHttpRequestResponseType::Text
: {
2116 if (mResponseData
->mResponseText
.IsEmpty()) {
2117 aResponse
.set(JS_GetEmptyStringValue(aCx
));
2121 XMLHttpRequestStringSnapshotReaderHelper
helper(
2122 mResponseData
->mResponseText
);
2124 str
= JS_NewUCStringCopyN(aCx
, helper
.Buffer(), helper
.Length());
2126 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
2130 aResponse
.setString(str
);
2134 case XMLHttpRequestResponseType::Arraybuffer
: {
2135 if (!mResponseData
->mResponseArrayBufferBuilder
) {
2136 aResponse
.setNull();
2140 if (!mResponseArrayBufferValue
) {
2141 mResponseArrayBufferValue
=
2142 mResponseData
->mResponseArrayBufferBuilder
->TakeArrayBuffer(aCx
);
2143 if (!mResponseArrayBufferValue
) {
2144 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
2149 aResponse
.setObject(*mResponseArrayBufferValue
);
2153 case XMLHttpRequestResponseType::Blob
: {
2154 if (!mResponseData
->mResponseBlobImpl
) {
2155 aResponse
.setNull();
2159 if (!mResponseBlob
) {
2161 Blob::Create(GetOwnerGlobal(), mResponseData
->mResponseBlobImpl
);
2164 if (!mResponseBlob
||
2165 !GetOrCreateDOMReflector(aCx
, mResponseBlob
, aResponse
)) {
2166 aResponse
.setNull();
2172 case XMLHttpRequestResponseType::Json
: {
2173 if (mResponseData
->mResponseJSON
.IsVoid()) {
2174 aResponse
.setNull();
2178 if (mResponseJSONValue
.isUndefined()) {
2179 // The Unicode converter has already zapped the BOM if there was one
2180 JS::Rooted
<JS::Value
> value(aCx
);
2181 if (!JS_ParseJSON(aCx
, mResponseData
->mResponseJSON
.BeginReading(),
2182 mResponseData
->mResponseJSON
.Length(), &value
)) {
2183 JS_ClearPendingException(aCx
);
2184 mResponseJSONValue
.setNull();
2186 mResponseJSONValue
= value
;
2189 mResponseData
->mResponseJSON
.Truncate();
2192 aResponse
.set(mResponseJSONValue
);
2197 MOZ_ASSERT_UNREACHABLE("Invalid type");
2198 aResponse
.setNull();
2203 void XMLHttpRequestWorker::GetResponseText(DOMString
& aResponseText
,
2205 MOZ_DIAGNOSTIC_ASSERT(mResponseData
);
2207 if (mResponseType
!= XMLHttpRequestResponseType::_empty
&&
2208 mResponseType
!= XMLHttpRequestResponseType::Text
) {
2209 aRv
.ThrowInvalidStateError(
2210 "responseText is only available if responseType is '' or 'text'.");
2214 if (!mResponseData
->mResponseText
.GetAsString(aResponseText
)) {
2215 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
2220 void XMLHttpRequestWorker::UpdateState(
2221 UniquePtr
<StateData
>&& aStateData
,
2222 UniquePtr
<ResponseData
>&& aResponseData
) {
2223 mStateData
= std::move(aStateData
);
2225 UniquePtr
<ResponseData
> responseData
= std::move(aResponseData
);
2227 ResetResponseData();
2228 mResponseData
= std::move(responseData
);
2231 XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
2234 void XMLHttpRequestWorker::ResetResponseData() {
2235 mResponseBlob
= nullptr;
2236 mResponseArrayBufferValue
= nullptr;
2237 mResponseJSONValue
.setUndefined();
2240 } // namespace mozilla::dom