Bug 1876335 - use GRADLE_MAVEN_REPOSITORIES in more places. r=owlish,geckoview-review...
[gecko.git] / dom / xhr / XMLHttpRequestMainThread.cpp
blobace26f296fcaae114578a51bfa7825b44680b34b
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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "XMLHttpRequestMainThread.h"
9 #include <algorithm>
10 #ifndef XP_WIN
11 # include <unistd.h>
12 #endif
13 #include "mozilla/ArrayUtils.h"
14 #include "mozilla/BasePrincipal.h"
15 #include "mozilla/CheckedInt.h"
16 #include "mozilla/Components.h"
17 #include "mozilla/dom/AutoSuppressEventHandlingAndSuspend.h"
18 #include "mozilla/dom/BlobBinding.h"
19 #include "mozilla/dom/BlobURLProtocolHandler.h"
20 #include "mozilla/dom/DocGroup.h"
21 #include "mozilla/dom/DOMString.h"
22 #include "mozilla/dom/File.h"
23 #include "mozilla/dom/FileBinding.h"
24 #include "mozilla/dom/FileCreatorHelper.h"
25 #include "mozilla/dom/FetchUtil.h"
26 #include "mozilla/dom/FormData.h"
27 #include "mozilla/dom/quota/QuotaCommon.h"
28 #include "mozilla/dom/MutableBlobStorage.h"
29 #include "mozilla/dom/XMLDocument.h"
30 #include "mozilla/dom/URLSearchParams.h"
31 #include "mozilla/dom/UserActivation.h"
32 #include "mozilla/dom/Promise.h"
33 #include "mozilla/dom/PromiseNativeHandler.h"
34 #include "mozilla/dom/ReferrerInfo.h"
35 #include "mozilla/dom/WorkerError.h"
36 #include "mozilla/Encoding.h"
37 #include "mozilla/EventDispatcher.h"
38 #include "mozilla/EventListenerManager.h"
39 #include "mozilla/HoldDropJSObjects.h"
40 #include "mozilla/LoadInfo.h"
41 #include "mozilla/LoadContext.h"
42 #include "mozilla/MemoryReporting.h"
43 #include "mozilla/net/ContentRange.h"
44 #include "mozilla/PreloaderBase.h"
45 #include "mozilla/ScopeExit.h"
46 #include "mozilla/SpinEventLoopUntil.h"
47 #include "mozilla/StaticPrefs_dom.h"
48 #include "mozilla/StaticPrefs_network.h"
49 #include "mozilla/StaticPrefs_privacy.h"
50 #include "mozilla/dom/ProgressEvent.h"
51 #include "nsDataChannel.h"
52 #include "nsIBaseChannel.h"
53 #include "nsIJARChannel.h"
54 #include "nsIJARURI.h"
55 #include "nsReadableUtils.h"
56 #include "nsSandboxFlags.h"
58 #include "nsIURI.h"
59 #include "nsIURIMutator.h"
60 #include "nsILoadGroup.h"
61 #include "nsNetUtil.h"
62 #include "nsStringStream.h"
63 #include "nsIAuthPrompt.h"
64 #include "nsIAuthPrompt2.h"
65 #include "nsIClassOfService.h"
66 #include "nsIHttpChannel.h"
67 #include "nsISupportsPriority.h"
68 #include "nsIInterfaceRequestorUtils.h"
69 #include "nsStreamUtils.h"
70 #include "nsThreadUtils.h"
71 #include "nsIUploadChannel.h"
72 #include "nsIUploadChannel2.h"
73 #include "nsXPCOM.h"
74 #include "nsIDOMEventListener.h"
75 #include "nsVariant.h"
76 #include "nsIScriptError.h"
77 #include "nsICachingChannel.h"
78 #include "nsICookieJarSettings.h"
79 #include "nsContentUtils.h"
80 #include "nsCycleCollectionParticipant.h"
81 #include "nsError.h"
82 #include "nsIPromptFactory.h"
83 #include "nsIWindowWatcher.h"
84 #include "nsIConsoleService.h"
85 #include "nsAsyncRedirectVerifyHelper.h"
86 #include "nsStringBuffer.h"
87 #include "nsIFileChannel.h"
88 #include "mozilla/Telemetry.h"
89 #include "js/ArrayBuffer.h" // JS::{Create,Release}MappedArrayBufferContents,New{,Mapped}ArrayBufferWithContents
90 #include "js/JSON.h" // JS_ParseJSON
91 #include "js/MemoryFunctions.h"
92 #include "js/RootingAPI.h" // JS::{{,Mutable}Handle,Rooted}
93 #include "js/Value.h" // JS::{,Undefined}Value
94 #include "jsapi.h" // JS_ClearPendingException
95 #include "GeckoProfiler.h"
96 #include "mozilla/dom/XMLHttpRequestBinding.h"
97 #include "mozilla/Attributes.h"
98 #include "MultipartBlobImpl.h"
99 #include "nsIPermissionManager.h"
100 #include "nsMimeTypes.h"
101 #include "nsIHttpChannelInternal.h"
102 #include "nsCharSeparatedTokenizer.h"
103 #include "nsStreamListenerWrapper.h"
104 #include "nsITimedChannel.h"
105 #include "nsWrapperCacheInlines.h"
106 #include "nsZipArchive.h"
107 #include "mozilla/Preferences.h"
108 #include "private/pprio.h"
109 #include "XMLHttpRequestUpload.h"
111 // Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being
112 // replaced by FileCreatorHelper#CreateFileW.
113 #ifdef CreateFile
114 # undef CreateFile
115 #endif
117 extern mozilla::LazyLogModule gXMLHttpRequestLog;
119 using namespace mozilla::net;
121 namespace mozilla::dom {
123 using EventType = XMLHttpRequest::EventType;
124 using Events = XMLHttpRequest::Events;
126 // Maximum size that we'll grow an ArrayBuffer instead of doubling,
127 // once doubling reaches this threshold
128 const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH = 32 * 1024 * 1024;
129 // start at 32k to avoid lots of doubling right at the start
130 const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE = 32 * 1024;
131 // the maximum Content-Length that we'll preallocate. 1GB. Must fit
132 // in an int32_t!
133 const int32_t XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE =
134 1 * 1024 * 1024 * 1024LL;
136 namespace {
137 const nsString kLiteralString_readystatechange = u"readystatechange"_ns;
138 const nsString kLiteralString_xmlhttprequest = u"xmlhttprequest"_ns;
139 const nsString kLiteralString_DOMContentLoaded = u"DOMContentLoaded"_ns;
140 const nsCString kLiteralString_charset = "charset"_ns;
141 const nsCString kLiteralString_UTF_8 = "UTF-8"_ns;
142 } // namespace
144 #define NS_PROGRESS_EVENT_INTERVAL 50
145 #define MAX_SYNC_TIMEOUT_WHEN_UNLOADING 10000 /* 10 secs */
147 NS_IMPL_ISUPPORTS(nsXHRParseEndListener, nsIDOMEventListener)
149 class nsResumeTimeoutsEvent : public Runnable {
150 public:
151 explicit nsResumeTimeoutsEvent(nsPIDOMWindowInner* aWindow)
152 : Runnable("dom::nsResumeTimeoutsEvent"), mWindow(aWindow) {}
154 NS_IMETHOD Run() override {
155 mWindow->Resume();
156 return NS_OK;
159 private:
160 nsCOMPtr<nsPIDOMWindowInner> mWindow;
163 // This helper function adds the given load flags to the request's existing
164 // load flags.
165 static void AddLoadFlags(nsIRequest* request, nsLoadFlags newFlags) {
166 nsLoadFlags flags;
167 request->GetLoadFlags(&flags);
168 flags |= newFlags;
169 request->SetLoadFlags(flags);
172 // We are in a sync event loop.
173 #define NOT_CALLABLE_IN_SYNC_SEND_RV \
174 if (mFlagSyncLooping || mEventDispatchingSuspended) { \
175 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT); \
176 return; \
179 /////////////////////////////////////////////
182 /////////////////////////////////////////////
184 #ifdef DEBUG
186 // In debug mode, annotate WorkerRefs with the name of the function being
187 // invoked for increased scrutability. Save the previous value on the stack.
188 namespace {
189 struct DebugWorkerRefs {
190 Mutex& mMutex;
191 RefPtr<ThreadSafeWorkerRef> mTSWorkerRef;
192 nsCString mPrev;
194 DebugWorkerRefs(XMLHttpRequestMainThread& aXHR, const std::string& aStatus)
195 : mMutex(aXHR.mTSWorkerRefMutex) {
196 MutexAutoLock lock(mMutex);
198 mTSWorkerRef = aXHR.mTSWorkerRef;
200 if (!mTSWorkerRef) {
201 return;
204 MOZ_ASSERT(mTSWorkerRef->Private());
206 nsCString status(aStatus.c_str());
207 mPrev = GET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref());
208 SET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref(), status);
211 ~DebugWorkerRefs() {
212 MutexAutoLock lock(mMutex);
214 if (!mTSWorkerRef) {
215 return;
218 MOZ_ASSERT(mTSWorkerRef->Private());
220 SET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref(), mPrev);
222 mTSWorkerRef = nullptr;
225 } // namespace
227 # define STREAM_STRING(stuff) \
228 (((const std::ostringstream&)(std::ostringstream() << stuff)) \
229 .str()) // NOLINT
231 # define DEBUG_WORKERREFS \
232 DebugWorkerRefs MOZ_UNIQUE_VAR(debugWR__)(*this, __func__)
234 # define DEBUG_WORKERREFS1(x) \
235 DebugWorkerRefs MOZ_UNIQUE_VAR(debugWR__)( \
236 *this, STREAM_STRING(__func__ << ": " << x)) // NOLINT
238 #else
239 # define DEBUG_WORKERREFS void()
240 # define DEBUG_WORKERREFS1(x) void()
241 #endif // DEBUG
243 bool XMLHttpRequestMainThread::sDontWarnAboutSyncXHR = false;
245 XMLHttpRequestMainThread::XMLHttpRequestMainThread(
246 nsIGlobalObject* aGlobalObject)
247 : XMLHttpRequest(aGlobalObject),
248 #ifdef DEBUG
249 mTSWorkerRefMutex("Debug WorkerRefs"),
250 #endif
251 mResponseBodyDecodedPos(0),
252 mResponseType(XMLHttpRequestResponseType::_empty),
253 mState(XMLHttpRequest_Binding::UNSENT),
254 mFlagSynchronous(false),
255 mFlagAborted(false),
256 mFlagParseBody(false),
257 mFlagSyncLooping(false),
258 mFlagBackgroundRequest(false),
259 mFlagHadUploadListenersOnSend(false),
260 mFlagACwithCredentials(false),
261 mFlagTimedOut(false),
262 mFlagDeleted(false),
263 mFlagSend(false),
264 mUploadTransferred(0),
265 mUploadTotal(0),
266 mUploadComplete(true),
267 mProgressSinceLastProgressEvent(false),
268 mRequestSentTime(0),
269 mTimeoutMilliseconds(0),
270 mErrorLoad(ErrorType::eOK),
271 mErrorLoadDetail(NS_OK),
272 mErrorParsingXML(false),
273 mWaitingForOnStopRequest(false),
274 mProgressTimerIsActive(false),
275 mIsHtml(false),
276 mWarnAboutSyncHtml(false),
277 mLoadTotal(-1),
278 mLoadTransferred(0),
279 mIsSystem(false),
280 mIsAnon(false),
281 mResultJSON(JS::UndefinedValue()),
282 mArrayBufferBuilder(new ArrayBufferBuilder()),
283 mResultArrayBuffer(nullptr),
284 mIsMappedArrayBuffer(false),
285 mXPCOMifier(nullptr),
286 mEventDispatchingSuspended(false),
287 mEofDecoded(false),
288 mDelayedDoneNotifier(nullptr) {
289 DEBUG_WORKERREFS;
290 mozilla::HoldJSObjects(this);
293 XMLHttpRequestMainThread::~XMLHttpRequestMainThread() {
294 DEBUG_WORKERREFS;
295 MOZ_ASSERT(
296 !mDelayedDoneNotifier,
297 "How can we have mDelayedDoneNotifier, which owns us, in destructor?");
299 mFlagDeleted = true;
301 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
302 mState == XMLHttpRequest_Binding::LOADING) {
303 Abort();
306 if (mParseEndListener) {
307 mParseEndListener->SetIsStale();
308 mParseEndListener = nullptr;
311 MOZ_ASSERT(!mFlagSyncLooping, "we rather crash than hang");
312 mFlagSyncLooping = false;
314 mozilla::DropJSObjects(this);
317 void XMLHttpRequestMainThread::Construct(
318 nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings,
319 bool aForWorker, nsIURI* aBaseURI /* = nullptr */,
320 nsILoadGroup* aLoadGroup /* = nullptr */,
321 PerformanceStorage* aPerformanceStorage /* = nullptr */,
322 nsICSPEventListener* aCSPEventListener /* = nullptr */) {
323 DEBUG_WORKERREFS;
324 MOZ_ASSERT(aPrincipal);
325 mPrincipal = aPrincipal;
326 mBaseURI = aBaseURI;
327 mLoadGroup = aLoadGroup;
328 mCookieJarSettings = aCookieJarSettings;
329 mForWorker = aForWorker;
330 mPerformanceStorage = aPerformanceStorage;
331 mCSPEventListener = aCSPEventListener;
334 void XMLHttpRequestMainThread::InitParameters(bool aAnon, bool aSystem) {
335 DEBUG_WORKERREFS;
336 if (!aAnon && !aSystem) {
337 return;
340 // Check for permissions.
341 // Chrome is always allowed access, so do the permission check only
342 // for non-chrome pages.
343 if (!IsSystemXHR() && aSystem) {
344 nsIGlobalObject* global = GetOwnerGlobal();
345 if (NS_WARN_IF(!global)) {
346 SetParameters(aAnon, false);
347 return;
350 nsIPrincipal* principal = global->PrincipalOrNull();
351 if (NS_WARN_IF(!principal)) {
352 SetParameters(aAnon, false);
353 return;
356 nsCOMPtr<nsIPermissionManager> permMgr =
357 components::PermissionManager::Service();
358 if (NS_WARN_IF(!permMgr)) {
359 SetParameters(aAnon, false);
360 return;
363 uint32_t permission;
364 nsresult rv = permMgr->TestPermissionFromPrincipal(
365 principal, "systemXHR"_ns, &permission);
366 if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) {
367 SetParameters(aAnon, false);
368 return;
372 SetParameters(aAnon, aSystem);
375 void XMLHttpRequestMainThread::SetClientInfoAndController(
376 const ClientInfo& aClientInfo,
377 const Maybe<ServiceWorkerDescriptor>& aController) {
378 mClientInfo.emplace(aClientInfo);
379 mController = aController;
382 void XMLHttpRequestMainThread::ResetResponse() {
383 mResponseXML = nullptr;
384 mResponseBody.Truncate();
385 TruncateResponseText();
386 mResponseBlobImpl = nullptr;
387 mResponseBlob = nullptr;
388 mBlobStorage = nullptr;
389 mResultArrayBuffer = nullptr;
390 mArrayBufferBuilder = new ArrayBufferBuilder();
391 mResultJSON.setUndefined();
392 mLoadTransferred = 0;
393 mResponseBodyDecodedPos = 0;
394 mEofDecoded = false;
397 NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestMainThread)
399 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestMainThread,
400 XMLHttpRequestEventTarget)
401 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
402 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
403 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML)
405 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener)
407 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseBlob)
408 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationCallbacks)
410 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink)
411 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink)
413 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
414 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
416 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestMainThread,
417 XMLHttpRequestEventTarget)
418 tmp->mResultArrayBuffer = nullptr;
419 tmp->mArrayBufferBuilder = nullptr;
420 tmp->mResultJSON.setUndefined();
421 tmp->mResponseBlobImpl = nullptr;
423 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
424 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
425 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML)
427 NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener)
429 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseBlob)
430 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationCallbacks)
432 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink)
433 NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink)
435 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
436 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
438 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestMainThread,
439 XMLHttpRequestEventTarget)
440 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
441 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultJSON)
442 NS_IMPL_CYCLE_COLLECTION_TRACE_END
444 bool XMLHttpRequestMainThread::IsCertainlyAliveForCC() const {
445 return mWaitingForOnStopRequest;
448 // QueryInterface implementation for XMLHttpRequestMainThread
449 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XMLHttpRequestMainThread)
450 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
451 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
452 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
453 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
454 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
455 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
456 NS_INTERFACE_MAP_ENTRY(nsINamed)
457 NS_INTERFACE_MAP_ENTRY(nsISizeOfEventTarget)
458 NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
460 NS_IMPL_ADDREF_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
461 NS_IMPL_RELEASE_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
463 void XMLHttpRequestMainThread::DisconnectFromOwner() {
464 XMLHttpRequestEventTarget::DisconnectFromOwner();
465 Abort();
468 size_t XMLHttpRequestMainThread::SizeOfEventTargetIncludingThis(
469 MallocSizeOf aMallocSizeOf) const {
470 size_t n = aMallocSizeOf(this);
471 n += mResponseBody.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
473 // Why is this safe? Because no-one else will report this string. The
474 // other possible sharers of this string are as follows.
476 // - The JS engine could hold copies if the JS code holds references, e.g.
477 // |var text = XHR.responseText|. However, those references will be via JS
478 // external strings, for which the JS memory reporter does *not* report the
479 // chars.
481 // - Binary extensions, but they're *extremely* unlikely to do any memory
482 // reporting.
484 n += mResponseText.SizeOfThis(aMallocSizeOf);
486 return n;
488 // Measurement of the following members may be added later if DMD finds it is
489 // worthwhile:
490 // - lots
493 static void LogMessage(
494 const char* aWarning, nsPIDOMWindowInner* aWindow,
495 const nsTArray<nsString>& aParams = nsTArray<nsString>()) {
496 nsCOMPtr<Document> doc;
497 if (aWindow) {
498 doc = aWindow->GetExtantDoc();
500 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, doc,
501 nsContentUtils::eDOM_PROPERTIES, aWarning,
502 aParams);
505 Document* XMLHttpRequestMainThread::GetResponseXML(ErrorResult& aRv) {
506 if (mResponseType != XMLHttpRequestResponseType::_empty &&
507 mResponseType != XMLHttpRequestResponseType::Document) {
508 aRv.ThrowInvalidStateError(
509 "responseXML is only available if responseType is '' or 'document'.");
510 return nullptr;
512 if (mWarnAboutSyncHtml) {
513 mWarnAboutSyncHtml = false;
514 LogMessage("HTMLSyncXHRWarning", GetOwner());
516 if (mState != XMLHttpRequest_Binding::DONE) {
517 return nullptr;
519 return mResponseXML;
523 * This piece copied from XMLDocument, we try to get the charset
524 * from HTTP headers.
526 nsresult XMLHttpRequestMainThread::DetectCharset() {
527 DEBUG_WORKERREFS;
528 mDecoder = nullptr;
530 if (mResponseType != XMLHttpRequestResponseType::_empty &&
531 mResponseType != XMLHttpRequestResponseType::Text &&
532 mResponseType != XMLHttpRequestResponseType::Json) {
533 return NS_OK;
536 nsAutoCString charsetVal;
537 const Encoding* encoding;
538 bool ok = mChannel && NS_SUCCEEDED(mChannel->GetContentCharset(charsetVal)) &&
539 (encoding = Encoding::ForLabel(charsetVal));
540 if (!ok) {
541 // MS documentation states UTF-8 is default for responseText
542 encoding = UTF_8_ENCODING;
545 if (mResponseType == XMLHttpRequestResponseType::Json &&
546 encoding != UTF_8_ENCODING) {
547 // The XHR spec says only UTF-8 is supported for responseType == "json"
548 LogMessage("JSONCharsetWarning", GetOwner());
549 encoding = UTF_8_ENCODING;
552 // Only sniff the BOM for non-JSON responseTypes
553 if (mResponseType == XMLHttpRequestResponseType::Json) {
554 mDecoder = encoding->NewDecoderWithBOMRemoval();
555 } else {
556 mDecoder = encoding->NewDecoder();
559 return NS_OK;
562 nsresult XMLHttpRequestMainThread::AppendToResponseText(
563 Span<const uint8_t> aBuffer, bool aLast) {
564 // Call this with an empty buffer to send the decoder the signal
565 // that we have hit the end of the stream.
567 NS_ENSURE_STATE(mDecoder);
569 CheckedInt<size_t> destBufferLen =
570 mDecoder->MaxUTF16BufferLength(aBuffer.Length());
572 { // scope for holding the mutex that protects mResponseText
573 XMLHttpRequestStringWriterHelper helper(mResponseText);
575 uint32_t len = helper.Length();
577 destBufferLen += len;
578 if (!destBufferLen.isValid() || destBufferLen.value() > UINT32_MAX) {
579 return NS_ERROR_OUT_OF_MEMORY;
582 auto handleOrErr = helper.BulkWrite(destBufferLen.value());
583 if (handleOrErr.isErr()) {
584 return handleOrErr.unwrapErr();
587 auto handle = handleOrErr.unwrap();
589 uint32_t result;
590 size_t read;
591 size_t written;
592 std::tie(result, read, written, std::ignore) =
593 mDecoder->DecodeToUTF16(aBuffer, handle.AsSpan().From(len), aLast);
594 MOZ_ASSERT(result == kInputEmpty);
595 MOZ_ASSERT(read == aBuffer.Length());
596 len += written;
597 MOZ_ASSERT(len <= destBufferLen.value());
598 handle.Finish(len, false);
599 } // release mutex
601 if (aLast) {
602 // Drop the finished decoder to avoid calling into a decoder
603 // that has finished.
604 mDecoder = nullptr;
605 mEofDecoded = true;
607 return NS_OK;
610 void XMLHttpRequestMainThread::GetResponseText(DOMString& aResponseText,
611 ErrorResult& aRv) {
612 MOZ_DIAGNOSTIC_ASSERT(!mForWorker);
614 XMLHttpRequestStringSnapshot snapshot;
615 GetResponseText(snapshot, aRv);
616 if (aRv.Failed()) {
617 return;
620 if (!snapshot.GetAsString(aResponseText)) {
621 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
622 return;
626 void XMLHttpRequestMainThread::GetResponseText(
627 XMLHttpRequestStringSnapshot& aSnapshot, ErrorResult& aRv) {
628 aSnapshot.Reset();
630 if (mResponseType != XMLHttpRequestResponseType::_empty &&
631 mResponseType != XMLHttpRequestResponseType::Text) {
632 aRv.ThrowInvalidStateError(
633 "responseText is only available if responseType is '' or 'text'.");
634 return;
637 if (mState != XMLHttpRequest_Binding::LOADING &&
638 mState != XMLHttpRequest_Binding::DONE) {
639 return;
642 // Main Fetch step 18 requires to ignore body for head/connect methods.
643 if (mRequestMethod.EqualsLiteral("HEAD") ||
644 mRequestMethod.EqualsLiteral("CONNECT")) {
645 return;
648 // We only decode text lazily if we're also parsing to a doc.
649 // Also, if we've decoded all current data already, then no need to decode
650 // more.
651 if ((!mResponseXML && !mErrorParsingXML) ||
652 (mResponseBodyDecodedPos == mResponseBody.Length() &&
653 (mState != XMLHttpRequest_Binding::DONE || mEofDecoded))) {
654 mResponseText.CreateSnapshot(aSnapshot);
655 return;
658 MatchCharsetAndDecoderToResponseDocument();
660 MOZ_ASSERT(mResponseBodyDecodedPos < mResponseBody.Length() ||
661 mState == XMLHttpRequest_Binding::DONE,
662 "Unexpected mResponseBodyDecodedPos");
663 Span<const uint8_t> span = mResponseBody;
664 aRv = AppendToResponseText(span.From(mResponseBodyDecodedPos),
665 mState == XMLHttpRequest_Binding::DONE);
666 if (aRv.Failed()) {
667 return;
670 mResponseBodyDecodedPos = mResponseBody.Length();
672 if (mEofDecoded) {
673 // Free memory buffer which we no longer need
674 mResponseBody.Truncate();
675 mResponseBodyDecodedPos = 0;
678 mResponseText.CreateSnapshot(aSnapshot);
681 nsresult XMLHttpRequestMainThread::CreateResponseParsedJSON(JSContext* aCx) {
682 if (!aCx) {
683 return NS_ERROR_FAILURE;
686 nsAutoString string;
687 nsresult rv = GetResponseTextForJSON(string);
688 if (NS_WARN_IF(NS_FAILED(rv))) {
689 return rv;
692 // The Unicode converter has already zapped the BOM if there was one
693 JS::Rooted<JS::Value> value(aCx);
694 if (!JS_ParseJSON(aCx, string.BeginReading(), string.Length(), &value)) {
695 return NS_ERROR_FAILURE;
698 mResultJSON = value;
699 return NS_OK;
702 void XMLHttpRequestMainThread::SetResponseType(
703 XMLHttpRequestResponseType aResponseType, ErrorResult& aRv) {
704 NOT_CALLABLE_IN_SYNC_SEND_RV
706 if (mState == XMLHttpRequest_Binding::LOADING ||
707 mState == XMLHttpRequest_Binding::DONE) {
708 aRv.ThrowInvalidStateError(
709 "Cannot set 'responseType' property on XMLHttpRequest after 'send()' "
710 "(when its state is LOADING or DONE).");
711 return;
714 // sync request is not allowed setting responseType in window context
715 if (HasOrHasHadOwner() && mState != XMLHttpRequest_Binding::UNSENT &&
716 mFlagSynchronous) {
717 LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
718 aRv.ThrowInvalidAccessError(
719 "synchronous XMLHttpRequests do not support timeout and responseType");
720 return;
723 // Set the responseType attribute's value to the given value.
724 SetResponseTypeRaw(aResponseType);
727 void XMLHttpRequestMainThread::GetResponse(
728 JSContext* aCx, JS::MutableHandle<JS::Value> aResponse, ErrorResult& aRv) {
729 MOZ_DIAGNOSTIC_ASSERT(!mForWorker);
731 switch (mResponseType) {
732 case XMLHttpRequestResponseType::_empty:
733 case XMLHttpRequestResponseType::Text: {
734 DOMString str;
735 GetResponseText(str, aRv);
736 if (aRv.Failed()) {
737 return;
739 if (!xpc::StringToJsval(aCx, str, aResponse)) {
740 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
742 return;
745 case XMLHttpRequestResponseType::Arraybuffer: {
746 if (mState != XMLHttpRequest_Binding::DONE) {
747 aResponse.setNull();
748 return;
751 if (!mResultArrayBuffer) {
752 mResultArrayBuffer = mArrayBufferBuilder->TakeArrayBuffer(aCx);
753 if (!mResultArrayBuffer) {
754 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
755 return;
758 aResponse.setObject(*mResultArrayBuffer);
759 return;
761 case XMLHttpRequestResponseType::Blob: {
762 if (mState != XMLHttpRequest_Binding::DONE) {
763 aResponse.setNull();
764 return;
767 if (!mResponseBlobImpl) {
768 aResponse.setNull();
769 return;
772 if (!mResponseBlob) {
773 mResponseBlob = Blob::Create(GetOwnerGlobal(), mResponseBlobImpl);
776 if (!GetOrCreateDOMReflector(aCx, mResponseBlob, aResponse)) {
777 aResponse.setNull();
780 return;
782 case XMLHttpRequestResponseType::Document: {
783 if (!mResponseXML || mState != XMLHttpRequest_Binding::DONE) {
784 aResponse.setNull();
785 return;
788 aRv =
789 nsContentUtils::WrapNative(aCx, ToSupports(mResponseXML), aResponse);
790 return;
792 case XMLHttpRequestResponseType::Json: {
793 if (mState != XMLHttpRequest_Binding::DONE) {
794 aResponse.setNull();
795 return;
798 if (mResultJSON.isUndefined()) {
799 aRv = CreateResponseParsedJSON(aCx);
800 TruncateResponseText();
801 if (aRv.Failed()) {
802 // Per spec, errors aren't propagated. null is returned instead.
803 aRv = NS_OK;
804 // It would be nice to log the error to the console. That's hard to
805 // do without calling window.onerror as a side effect, though.
806 JS_ClearPendingException(aCx);
807 mResultJSON.setNull();
810 aResponse.set(mResultJSON);
811 return;
813 default:
814 NS_ERROR("Should not happen");
817 aResponse.setNull();
820 already_AddRefed<BlobImpl> XMLHttpRequestMainThread::GetResponseBlobImpl() {
821 MOZ_DIAGNOSTIC_ASSERT(mForWorker);
822 MOZ_DIAGNOSTIC_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
824 if (mState != XMLHttpRequest_Binding::DONE) {
825 return nullptr;
828 RefPtr<BlobImpl> blobImpl = mResponseBlobImpl;
829 return blobImpl.forget();
832 already_AddRefed<ArrayBufferBuilder>
833 XMLHttpRequestMainThread::GetResponseArrayBufferBuilder() {
834 MOZ_DIAGNOSTIC_ASSERT(mForWorker);
835 MOZ_DIAGNOSTIC_ASSERT(mResponseType ==
836 XMLHttpRequestResponseType::Arraybuffer);
838 if (mState != XMLHttpRequest_Binding::DONE) {
839 return nullptr;
842 RefPtr<ArrayBufferBuilder> builder = mArrayBufferBuilder;
843 return builder.forget();
846 nsresult XMLHttpRequestMainThread::GetResponseTextForJSON(nsAString& aString) {
847 if (mState != XMLHttpRequest_Binding::DONE) {
848 aString.SetIsVoid(true);
849 return NS_OK;
852 if (!mResponseText.GetAsString(aString)) {
853 return NS_ERROR_OUT_OF_MEMORY;
856 return NS_OK;
859 bool XMLHttpRequestMainThread::IsCrossSiteCORSRequest() const {
860 if (!mChannel) {
861 return false;
864 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
865 return loadInfo->GetTainting() == LoadTainting::CORS;
868 bool XMLHttpRequestMainThread::IsDeniedCrossSiteCORSRequest() {
869 if (IsCrossSiteCORSRequest()) {
870 nsresult rv;
871 mChannel->GetStatus(&rv);
872 if (NS_FAILED(rv)) {
873 return true;
876 return false;
879 bool XMLHttpRequestMainThread::BadContentRangeRequested() {
880 if (!mChannel) {
881 return false;
883 // Only nsIBaseChannel supports this
884 nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel);
885 if (!baseChan) {
886 return false;
888 // A bad range was requested if the channel has no content range
889 // despite the request specifying a range header.
890 return !baseChan->ContentRange() && mAuthorRequestHeaders.Has("range");
893 RefPtr<mozilla::net::ContentRange>
894 XMLHttpRequestMainThread::GetRequestedContentRange() const {
895 MOZ_ASSERT(mChannel);
896 nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel);
897 if (!baseChan) {
898 return nullptr;
900 return baseChan->ContentRange();
903 void XMLHttpRequestMainThread::GetContentRangeHeader(nsACString& out) const {
904 if (!IsBlobURI(mRequestURL)) {
905 out.SetIsVoid(true);
906 return;
908 RefPtr<mozilla::net::ContentRange> range = GetRequestedContentRange();
909 if (range) {
910 range->AsHeader(out);
911 } else {
912 out.SetIsVoid(true);
916 void XMLHttpRequestMainThread::GetResponseURL(nsAString& aUrl) {
917 aUrl.Truncate();
919 if ((mState == XMLHttpRequest_Binding::UNSENT ||
920 mState == XMLHttpRequest_Binding::OPENED) ||
921 !mChannel) {
922 return;
925 // Make sure we don't leak responseURL information from denied cross-site
926 // requests.
927 if (IsDeniedCrossSiteCORSRequest()) {
928 return;
931 nsCOMPtr<nsIURI> responseUrl;
932 if (NS_FAILED(NS_GetFinalChannelURI(mChannel, getter_AddRefs(responseUrl)))) {
933 return;
936 nsAutoCString temp;
937 responseUrl->GetSpecIgnoringRef(temp);
938 CopyUTF8toUTF16(temp, aUrl);
941 uint32_t XMLHttpRequestMainThread::GetStatus(ErrorResult& aRv) {
942 // Make sure we don't leak status information from denied cross-site
943 // requests.
944 if (IsDeniedCrossSiteCORSRequest()) {
945 return 0;
948 if (mState == XMLHttpRequest_Binding::UNSENT ||
949 mState == XMLHttpRequest_Binding::OPENED) {
950 return 0;
953 if (mErrorLoad != ErrorType::eOK) {
954 // Let's simulate the http protocol for jar/app requests:
955 nsCOMPtr<nsIJARChannel> jarChannel = GetCurrentJARChannel();
956 if (jarChannel) {
957 nsresult status;
958 mChannel->GetStatus(&status);
960 if (status == NS_ERROR_FILE_NOT_FOUND) {
961 return 404; // Not Found
962 } else {
963 return 500; // Internal Error
967 return 0;
970 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
971 if (!httpChannel) {
972 // Pretend like we got a 200/206 response, since our load was successful
973 return GetRequestedContentRange() ? 206 : 200;
976 uint32_t status;
977 nsresult rv = httpChannel->GetResponseStatus(&status);
978 if (NS_FAILED(rv)) {
979 status = 0;
982 return status;
985 void XMLHttpRequestMainThread::GetStatusText(nsACString& aStatusText,
986 ErrorResult& aRv) {
987 // Return an empty status text on all error loads.
988 aStatusText.Truncate();
990 // Make sure we don't leak status information from denied cross-site
991 // requests.
992 if (IsDeniedCrossSiteCORSRequest()) {
993 return;
996 // Check the current XHR state to see if it is valid to obtain the statusText
997 // value. This check is to prevent the status text for redirects from being
998 // available before all the redirects have been followed and HTTP headers have
999 // been received.
1000 if (mState == XMLHttpRequest_Binding::UNSENT ||
1001 mState == XMLHttpRequest_Binding::OPENED) {
1002 return;
1005 if (mErrorLoad != ErrorType::eOK) {
1006 return;
1009 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
1010 if (httpChannel) {
1011 Unused << httpChannel->GetResponseStatusText(aStatusText);
1012 } else {
1013 aStatusText.AssignLiteral("OK");
1017 void XMLHttpRequestMainThread::TerminateOngoingFetch(nsresult detail) {
1018 DEBUG_WORKERREFS;
1019 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
1020 mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
1021 mState == XMLHttpRequest_Binding::LOADING) {
1022 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Info,
1023 ("%p TerminateOngoingFetch(0x%" PRIx32 ")", this,
1024 static_cast<uint32_t>(detail)));
1025 CloseRequest(detail);
1029 void XMLHttpRequestMainThread::CloseRequest(nsresult detail) {
1030 DEBUG_WORKERREFS;
1031 mWaitingForOnStopRequest = false;
1032 mErrorLoad = ErrorType::eTerminated;
1033 mErrorLoadDetail = detail;
1034 if (mChannel) {
1035 mChannel->CancelWithReason(NS_BINDING_ABORTED,
1036 "XMLHttpRequestMainThread::CloseRequest"_ns);
1038 CancelTimeoutTimer();
1041 void XMLHttpRequestMainThread::CloseRequestWithError(
1042 const ErrorProgressEventType& aType) {
1043 DEBUG_WORKERREFS;
1044 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
1045 ("%p CloseRequestWithError(%s)", this, aType.cStr));
1047 CloseRequest(aType.errorCode);
1049 ResetResponse();
1051 // If we're in the destructor, don't risk dispatching an event.
1052 if (mFlagDeleted) {
1053 mFlagSyncLooping = false;
1054 return;
1057 if (mState != XMLHttpRequest_Binding::UNSENT &&
1058 !(mState == XMLHttpRequest_Binding::OPENED && !mFlagSend) &&
1059 mState != XMLHttpRequest_Binding::DONE) {
1060 ChangeState(XMLHttpRequest_Binding::DONE, true);
1062 if (!mFlagSyncLooping) {
1063 if (mUpload && !mUploadComplete) {
1064 mUploadComplete = true;
1065 DispatchProgressEvent(mUpload, aType, 0, -1);
1067 DispatchProgressEvent(this, aType, 0, -1);
1071 // The ChangeState call above calls onreadystatechange handlers which
1072 // if they load a new url will cause XMLHttpRequestMainThread::Open to clear
1073 // the abort state bit. If this occurs we're not uninitialized (bug 361773).
1074 if (mFlagAborted) {
1075 ChangeState(XMLHttpRequest_Binding::UNSENT, false); // IE seems to do it
1078 mFlagSyncLooping = false;
1081 void XMLHttpRequestMainThread::RequestErrorSteps(
1082 const ProgressEventType aEventType, const nsresult aOptionalException,
1083 ErrorResult& aRv) {
1084 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
1085 ("%p RequestErrorSteps(%s,0x%" PRIx32 ")", this, aEventType.cStr,
1086 static_cast<uint32_t>(aOptionalException)));
1088 // Cancel our timers first before setting our state to done, so we don't
1089 // trip any assertions if one fires and asserts that state != done.
1090 CancelTimeoutTimer();
1091 CancelSyncTimeoutTimer();
1092 StopProgressEventTimer();
1094 // Step 1
1095 mState = XMLHttpRequest_Binding::DONE;
1097 // Step 2
1098 mFlagSend = false;
1100 // Step 3
1101 ResetResponse();
1103 // If we're in the destructor, don't risk dispatching an event.
1104 if (mFlagDeleted) {
1105 mFlagSyncLooping = false;
1106 return;
1109 // Step 4
1110 if (mFlagSynchronous && NS_FAILED(aOptionalException)) {
1111 aRv.Throw(aOptionalException);
1112 return;
1115 // Step 5
1116 FireReadystatechangeEvent();
1118 // Step 6
1119 if (mUpload && !mUploadComplete) {
1120 // Step 6-1
1121 mUploadComplete = true;
1123 // Step 6-2
1124 if (mFlagHadUploadListenersOnSend) {
1125 // Steps 6-3, 6-4 (loadend is fired for us)
1126 DispatchProgressEvent(mUpload, aEventType, 0, -1);
1130 // Steps 7 and 8 (loadend is fired for us)
1131 DispatchProgressEvent(this, aEventType, 0, -1);
1134 void XMLHttpRequestMainThread::Abort(ErrorResult& aRv) {
1135 NOT_CALLABLE_IN_SYNC_SEND_RV
1136 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("%p Abort()", this));
1137 AbortInternal(aRv);
1140 void XMLHttpRequestMainThread::AbortInternal(ErrorResult& aRv) {
1141 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("%p AbortInternal()", this));
1142 mFlagAborted = true;
1143 DisconnectDoneNotifier();
1145 // Step 1
1146 TerminateOngoingFetch(NS_ERROR_DOM_ABORT_ERR);
1148 // Step 2
1149 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
1150 mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
1151 mState == XMLHttpRequest_Binding::LOADING) {
1152 RequestErrorSteps(Events::abort, NS_ERROR_DOM_ABORT_ERR, aRv);
1155 // Step 3
1156 if (mState == XMLHttpRequest_Binding::DONE) {
1157 ChangeState(XMLHttpRequest_Binding::UNSENT,
1158 false); // no ReadystateChange event
1161 mFlagSyncLooping = false;
1164 /*Method that checks if it is safe to expose a header value to the client.
1165 It is used to check what headers are exposed for CORS requests.*/
1166 bool XMLHttpRequestMainThread::IsSafeHeader(
1167 const nsACString& aHeader, NotNull<nsIHttpChannel*> aHttpChannel) const {
1168 // See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
1169 if (!IsSystemXHR() && nsContentUtils::IsForbiddenResponseHeader(aHeader)) {
1170 NS_WARNING("blocked access to response header");
1171 return false;
1173 // if this is not a CORS call all headers are safe
1174 if (!IsCrossSiteCORSRequest()) {
1175 return true;
1177 // Check for dangerous headers
1178 // Make sure we don't leak header information from denied cross-site
1179 // requests.
1180 if (mChannel) {
1181 nsresult status;
1182 mChannel->GetStatus(&status);
1183 if (NS_FAILED(status)) {
1184 return false;
1187 const char* kCrossOriginSafeHeaders[] = {
1188 "cache-control", "content-language", "content-type", "content-length",
1189 "expires", "last-modified", "pragma"};
1190 for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
1191 if (aHeader.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
1192 return true;
1195 nsAutoCString headerVal;
1196 // The "Access-Control-Expose-Headers" header contains a comma separated
1197 // list of method names.
1198 Unused << aHttpChannel->GetResponseHeader("Access-Control-Expose-Headers"_ns,
1199 headerVal);
1200 bool isSafe = false;
1201 for (const nsACString& token :
1202 nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
1203 if (token.IsEmpty()) {
1204 continue;
1206 if (!NS_IsValidHTTPToken(token)) {
1207 return false;
1210 if (token.EqualsLiteral("*") && !mFlagACwithCredentials) {
1211 isSafe = true;
1212 } else if (aHeader.Equals(token, nsCaseInsensitiveCStringComparator)) {
1213 isSafe = true;
1217 return isSafe;
1220 bool XMLHttpRequestMainThread::GetContentType(nsACString& aValue) const {
1221 MOZ_ASSERT(mChannel);
1222 nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel);
1223 if (baseChan) {
1224 RefPtr<CMimeType> fullMimeType(baseChan->FullMimeType());
1225 if (fullMimeType) {
1226 fullMimeType->Serialize(aValue);
1227 return true;
1230 if (NS_SUCCEEDED(mChannel->GetContentType(aValue))) {
1231 nsCString value;
1232 if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) {
1233 aValue.AppendLiteral(";charset=");
1234 aValue.Append(value);
1236 return true;
1238 return false;
1240 void XMLHttpRequestMainThread::GetAllResponseHeaders(
1241 nsACString& aResponseHeaders, ErrorResult& aRv) {
1242 NOT_CALLABLE_IN_SYNC_SEND_RV
1244 aResponseHeaders.Truncate();
1246 // If the state is UNSENT or OPENED,
1247 // return the empty string and terminate these steps.
1248 if (mState == XMLHttpRequest_Binding::UNSENT ||
1249 mState == XMLHttpRequest_Binding::OPENED) {
1250 return;
1253 if (mErrorLoad != ErrorType::eOK) {
1254 return;
1257 if (nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel()) {
1258 RefPtr<nsHeaderVisitor> visitor =
1259 new nsHeaderVisitor(*this, WrapNotNull(httpChannel));
1260 if (NS_SUCCEEDED(httpChannel->VisitResponseHeaders(visitor))) {
1261 aResponseHeaders = visitor->Headers();
1263 return;
1266 if (!mChannel) {
1267 return;
1270 // Even non-http channels supply content type.
1271 nsAutoCString value;
1272 if (GetContentType(value)) {
1273 aResponseHeaders.AppendLiteral("Content-Type: ");
1274 aResponseHeaders.Append(value);
1275 aResponseHeaders.AppendLiteral("\r\n");
1278 // Don't provide Content-Length for data URIs
1279 nsCOMPtr<nsIURI> uri;
1280 if (NS_FAILED(mChannel->GetURI(getter_AddRefs(uri))) ||
1281 !uri->SchemeIs("data")) {
1282 int64_t length;
1283 if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1284 aResponseHeaders.AppendLiteral("Content-Length: ");
1285 aResponseHeaders.AppendInt(length);
1286 aResponseHeaders.AppendLiteral("\r\n");
1290 // Should set a Content-Range header for blob scheme.
1291 // From https://fetch.spec.whatwg.org/#scheme-fetch 3.blob.9.20:
1292 // "Set response’s header list to «(`Content-Length`, serializedSlicedLength),
1293 // (`Content-Type`, type), (`Content-Range`, contentRange)»."
1294 GetContentRangeHeader(value);
1295 if (!value.IsVoid()) {
1296 aResponseHeaders.AppendLiteral("Content-Range: ");
1297 aResponseHeaders.Append(value);
1298 aResponseHeaders.AppendLiteral("\r\n");
1302 void XMLHttpRequestMainThread::GetResponseHeader(const nsACString& header,
1303 nsACString& _retval,
1304 ErrorResult& aRv) {
1305 NOT_CALLABLE_IN_SYNC_SEND_RV
1307 _retval.SetIsVoid(true);
1309 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
1311 if (!httpChannel) {
1312 // If the state is UNSENT or OPENED,
1313 // return null and terminate these steps.
1314 if (mState == XMLHttpRequest_Binding::UNSENT ||
1315 mState == XMLHttpRequest_Binding::OPENED) {
1316 return;
1319 // Even non-http channels supply content type and content length.
1320 // Remember we don't leak header information from denied cross-site
1321 // requests. However, we handle file: and blob: URLs for blob response
1322 // types by canceling them with a specific error, so we have to allow
1323 // them to pass through this check.
1324 nsresult status;
1325 if (!mChannel || NS_FAILED(mChannel->GetStatus(&status)) ||
1326 (NS_FAILED(status) && status != NS_ERROR_FILE_ALREADY_EXISTS)) {
1327 return;
1330 // Content Type:
1331 if (header.LowerCaseEqualsASCII("content-type")) {
1332 if (!GetContentType(_retval)) {
1333 // Means no content type
1334 _retval.SetIsVoid(true);
1335 return;
1339 // Content Length:
1340 else if (header.LowerCaseEqualsASCII("content-length")) {
1341 int64_t length;
1342 if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1343 _retval.AppendInt(length);
1347 // Content Range:
1348 else if (header.LowerCaseEqualsASCII("content-range")) {
1349 GetContentRangeHeader(_retval);
1352 return;
1355 // Check for dangerous headers
1356 if (!IsSafeHeader(header, WrapNotNull(httpChannel))) {
1357 return;
1360 aRv = httpChannel->GetResponseHeader(header, _retval);
1361 if (aRv.ErrorCodeIs(NS_ERROR_NOT_AVAILABLE)) {
1362 // Means no header
1363 _retval.SetIsVoid(true);
1364 aRv.SuppressException();
1368 already_AddRefed<nsILoadGroup> XMLHttpRequestMainThread::GetLoadGroup() const {
1369 if (mFlagBackgroundRequest) {
1370 return nullptr;
1373 if (mLoadGroup) {
1374 nsCOMPtr<nsILoadGroup> ref = mLoadGroup;
1375 return ref.forget();
1378 Document* doc = GetDocumentIfCurrent();
1379 if (doc) {
1380 return doc->GetDocumentLoadGroup();
1383 return nullptr;
1386 nsresult XMLHttpRequestMainThread::FireReadystatechangeEvent() {
1387 MOZ_ASSERT(mState != XMLHttpRequest_Binding::UNSENT);
1388 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1389 event->InitEvent(kLiteralString_readystatechange, false, false);
1390 // We assume anyone who managed to call CreateReadystatechangeEvent is trusted
1391 event->SetTrusted(true);
1392 DispatchOrStoreEvent(this, event);
1393 return NS_OK;
1396 void XMLHttpRequestMainThread::DispatchProgressEvent(
1397 DOMEventTargetHelper* aTarget, const ProgressEventType& aType,
1398 int64_t aLoaded, int64_t aTotal) {
1399 DEBUG_WORKERREFS;
1400 NS_ASSERTION(aTarget, "null target");
1402 if (NS_FAILED(CheckCurrentGlobalCorrectness()) ||
1403 (!AllowUploadProgress() && aTarget == mUpload)) {
1404 return;
1407 // If blocked by CORS, zero-out the stats on progress events
1408 // and never fire "progress" or "load" events at all.
1409 if (IsDeniedCrossSiteCORSRequest()) {
1410 if (aType == Events::progress || aType == Events::load) {
1411 return;
1413 aLoaded = 0;
1414 aTotal = -1;
1417 ProgressEventInit init;
1418 init.mBubbles = false;
1419 init.mCancelable = false;
1420 init.mLengthComputable = aTotal != -1; // XHR spec step 6.1
1421 init.mLoaded = aLoaded;
1422 init.mTotal = (aTotal == -1) ? 0 : aTotal;
1424 RefPtr<ProgressEvent> event =
1425 ProgressEvent::Constructor(aTarget, aType, init);
1426 event->SetTrusted(true);
1428 MOZ_LOG(
1429 gXMLHttpRequestLog, LogLevel::Debug,
1430 ("firing %s event (%u,%u,%" PRIu64 ",%" PRIu64 ")", aType.cStr,
1431 aTarget == mUpload, aTotal != -1, aLoaded, (aTotal == -1) ? 0 : aTotal));
1433 DispatchOrStoreEvent(aTarget, event);
1435 // If we're sending a load, error, timeout or abort event, then
1436 // also dispatch the subsequent loadend event.
1437 if (aType == Events::load || aType == Events::error ||
1438 aType == Events::timeout || aType == Events::abort) {
1439 DispatchProgressEvent(aTarget, Events::loadend, aLoaded, aTotal);
1443 void XMLHttpRequestMainThread::DispatchOrStoreEvent(
1444 DOMEventTargetHelper* aTarget, Event* aEvent) {
1445 DEBUG_WORKERREFS;
1446 MOZ_ASSERT(aTarget);
1447 MOZ_ASSERT(aEvent);
1449 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
1450 return;
1453 if (mEventDispatchingSuspended) {
1454 PendingEvent* event = mPendingEvents.AppendElement();
1455 event->mTarget = aTarget;
1456 event->mEvent = aEvent;
1457 return;
1460 aTarget->DispatchEvent(*aEvent);
1463 void XMLHttpRequestMainThread::SuspendEventDispatching() {
1464 MOZ_ASSERT(!mEventDispatchingSuspended);
1465 mEventDispatchingSuspended = true;
1468 void XMLHttpRequestMainThread::ResumeEventDispatching() {
1469 MOZ_ASSERT(mEventDispatchingSuspended);
1470 mEventDispatchingSuspended = false;
1472 nsTArray<PendingEvent> pendingEvents = std::move(mPendingEvents);
1474 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
1475 return;
1478 for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
1479 pendingEvents[i].mTarget->DispatchEvent(*pendingEvents[i].mEvent);
1483 already_AddRefed<nsIHttpChannel>
1484 XMLHttpRequestMainThread::GetCurrentHttpChannel() {
1485 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
1486 return httpChannel.forget();
1489 already_AddRefed<nsIJARChannel>
1490 XMLHttpRequestMainThread::GetCurrentJARChannel() {
1491 nsCOMPtr<nsIJARChannel> appChannel = do_QueryInterface(mChannel);
1492 return appChannel.forget();
1495 bool XMLHttpRequestMainThread::IsSystemXHR() const {
1496 return mIsSystem || mPrincipal->IsSystemPrincipal();
1499 bool XMLHttpRequestMainThread::InUploadPhase() const {
1500 // We're in the upload phase while our state is OPENED.
1501 return mState == XMLHttpRequest_Binding::OPENED;
1504 // This case is hit when the async parameter is outright omitted, which
1505 // should set it to true (and the username and password to null).
1506 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1507 const nsAString& aUrl, ErrorResult& aRv) {
1508 Open(aMethod, aUrl, true, VoidString(), VoidString(), aRv);
1511 // This case is hit when the async parameter is specified, even if the
1512 // JS value was "undefined" (which due to legacy reasons should be
1513 // treated as true, which is how it will already be passed in here).
1514 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1515 const nsAString& aUrl, bool aAsync,
1516 const nsAString& aUsername,
1517 const nsAString& aPassword,
1518 ErrorResult& aRv) {
1519 Open(aMethod, NS_ConvertUTF16toUTF8(aUrl), aAsync, aUsername, aPassword, aRv);
1522 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1523 const nsACString& aUrl, bool aAsync,
1524 const nsAString& aUsername,
1525 const nsAString& aPassword,
1526 ErrorResult& aRv) {
1527 DEBUG_WORKERREFS1(aMethod << " " << aUrl);
1528 NOT_CALLABLE_IN_SYNC_SEND_RV
1530 // Gecko-specific
1531 if (!aAsync && !DontWarnAboutSyncXHR() && GetOwner() &&
1532 GetOwner()->GetExtantDoc()) {
1533 GetOwner()->GetExtantDoc()->WarnOnceAbout(
1534 DeprecatedOperations::eSyncXMLHttpRequestDeprecated);
1537 Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC,
1538 aAsync ? 0 : 1);
1540 // Step 1
1541 nsCOMPtr<Document> responsibleDocument = GetDocumentIfCurrent();
1542 if (!responsibleDocument) {
1543 // This could be because we're no longer current or because we're in some
1544 // non-window context...
1545 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
1546 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
1547 return;
1550 if (!mPrincipal) {
1551 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
1552 return;
1555 // Gecko-specific
1556 if (!aAsync && responsibleDocument && GetOwner()) {
1557 // We have no extant document during unload, so the above general
1558 // syncXHR warning will not display. But we do want to display a
1559 // recommendation to use sendBeacon instead of syncXHR during unload.
1560 nsCOMPtr<nsIDocShell> shell = responsibleDocument->GetDocShell();
1561 if (shell) {
1562 bool inUnload = false;
1563 shell->GetIsInUnload(&inUnload);
1564 if (inUnload) {
1565 LogMessage("UseSendBeaconDuringUnloadAndPagehideWarning", GetOwner());
1570 // Steps 2-4
1571 nsAutoCString method;
1572 aRv = FetchUtil::GetValidRequestMethod(aMethod, method);
1573 if (NS_WARN_IF(aRv.Failed())) {
1574 return;
1577 // Steps 5-6
1578 nsIURI* baseURI = nullptr;
1579 if (mBaseURI) {
1580 baseURI = mBaseURI;
1581 } else if (responsibleDocument) {
1582 baseURI = responsibleDocument->GetBaseURI();
1585 // Use the responsible document's encoding for the URL if we have one,
1586 // except for dedicated workers. Use UTF-8 otherwise.
1587 NotNull<const Encoding*> originCharset = UTF_8_ENCODING;
1588 if (responsibleDocument &&
1589 responsibleDocument->NodePrincipal() == mPrincipal) {
1590 originCharset = responsibleDocument->GetDocumentCharacterSet();
1593 nsCOMPtr<nsIURI> parsedURL;
1594 nsresult rv =
1595 NS_NewURI(getter_AddRefs(parsedURL), aUrl, originCharset, baseURI);
1596 if (NS_FAILED(rv)) {
1597 aRv.ThrowSyntaxError("'"_ns + aUrl + "' is not a valid URL."_ns);
1598 return;
1600 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
1601 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
1602 return;
1605 // Step 7
1606 // This is already handled by the other Open() method, which passes
1607 // username and password in as NullStrings.
1609 // Step 8
1610 nsAutoCString host;
1611 parsedURL->GetHost(host);
1612 if (!host.IsEmpty() && (!aUsername.IsVoid() || !aPassword.IsVoid())) {
1613 auto mutator = NS_MutateURI(parsedURL);
1614 if (!aUsername.IsVoid()) {
1615 mutator.SetUsername(NS_ConvertUTF16toUTF8(aUsername));
1617 if (!aPassword.IsVoid()) {
1618 mutator.SetPassword(NS_ConvertUTF16toUTF8(aPassword));
1620 Unused << mutator.Finalize(parsedURL);
1623 // Step 9
1624 if (!aAsync && HasOrHasHadOwner() &&
1625 (mTimeoutMilliseconds ||
1626 mResponseType != XMLHttpRequestResponseType::_empty)) {
1627 if (mTimeoutMilliseconds) {
1628 LogMessage("TimeoutSyncXHRWarning", GetOwner());
1630 if (mResponseType != XMLHttpRequestResponseType::_empty) {
1631 LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
1633 aRv.ThrowInvalidAccessError(
1634 "synchronous XMLHttpRequests do not support timeout and responseType");
1635 return;
1638 // Step 10
1639 TerminateOngoingFetch(NS_OK);
1641 // Step 11
1642 // timeouts are handled without a flag
1643 DisconnectDoneNotifier();
1644 mFlagSend = false;
1645 mRequestMethod.Assign(method);
1646 mRequestURL = parsedURL;
1647 mFlagSynchronous = !aAsync;
1648 mAuthorRequestHeaders.Clear();
1649 ResetResponse();
1651 // Gecko-specific
1652 mFlagHadUploadListenersOnSend = false;
1653 mFlagAborted = false;
1654 mFlagTimedOut = false;
1655 mDecoder = nullptr;
1657 // Per spec we should only create the channel on send(), but we have internal
1658 // code that relies on the channel being created now, and that code is not
1659 // always IsSystemXHR(). However, we're not supposed to throw channel-creation
1660 // errors during open(), so we silently ignore those here.
1661 CreateChannel();
1663 // Step 12
1664 if (mState != XMLHttpRequest_Binding::OPENED) {
1665 mState = XMLHttpRequest_Binding::OPENED;
1666 FireReadystatechangeEvent();
1670 void XMLHttpRequestMainThread::SetOriginAttributes(
1671 const OriginAttributesDictionary& aAttrs) {
1672 MOZ_ASSERT((mState == XMLHttpRequest_Binding::OPENED) && !mFlagSend);
1674 OriginAttributes attrs(aAttrs);
1676 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
1677 loadInfo->SetOriginAttributes(attrs);
1681 * "Copy" from a stream.
1683 nsresult XMLHttpRequestMainThread::StreamReaderFunc(
1684 nsIInputStream* in, void* closure, const char* fromRawSegment,
1685 uint32_t toOffset, uint32_t count, uint32_t* writeCount) {
1686 XMLHttpRequestMainThread* xmlHttpRequest =
1687 static_cast<XMLHttpRequestMainThread*>(closure);
1688 if (!xmlHttpRequest || !writeCount) {
1689 NS_WARNING(
1690 "XMLHttpRequest cannot read from stream: no closure or writeCount");
1691 return NS_ERROR_FAILURE;
1694 nsresult rv = NS_OK;
1696 if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Blob) {
1697 xmlHttpRequest->MaybeCreateBlobStorage();
1698 rv = xmlHttpRequest->mBlobStorage->Append(fromRawSegment, count);
1699 } else if (xmlHttpRequest->mResponseType ==
1700 XMLHttpRequestResponseType::Arraybuffer &&
1701 !xmlHttpRequest->mIsMappedArrayBuffer) {
1702 // get the initial capacity to something reasonable to avoid a bunch of
1703 // reallocs right at the start
1704 if (xmlHttpRequest->mArrayBufferBuilder->Capacity() == 0)
1705 xmlHttpRequest->mArrayBufferBuilder->SetCapacity(
1706 std::max(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE));
1708 if (NS_WARN_IF(!xmlHttpRequest->mArrayBufferBuilder->Append(
1709 reinterpret_cast<const uint8_t*>(fromRawSegment), count,
1710 XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH))) {
1711 return NS_ERROR_OUT_OF_MEMORY;
1714 } else if (xmlHttpRequest->mResponseType ==
1715 XMLHttpRequestResponseType::_empty &&
1716 xmlHttpRequest->mResponseXML) {
1717 // Copy for our own use
1718 if (!xmlHttpRequest->mResponseBody.Append(fromRawSegment, count,
1719 fallible)) {
1720 return NS_ERROR_OUT_OF_MEMORY;
1722 } else if (xmlHttpRequest->mResponseType ==
1723 XMLHttpRequestResponseType::_empty ||
1724 xmlHttpRequest->mResponseType ==
1725 XMLHttpRequestResponseType::Text ||
1726 xmlHttpRequest->mResponseType ==
1727 XMLHttpRequestResponseType::Json) {
1728 MOZ_ASSERT(!xmlHttpRequest->mResponseXML,
1729 "We shouldn't be parsing a doc here");
1730 rv = xmlHttpRequest->AppendToResponseText(
1731 AsBytes(Span(fromRawSegment, count)));
1732 if (NS_WARN_IF(NS_FAILED(rv))) {
1733 return rv;
1737 if (xmlHttpRequest->mFlagParseBody) {
1738 // Give the same data to the parser.
1740 // We need to wrap the data in a new lightweight stream and pass that
1741 // to the parser, because calling ReadSegments() recursively on the same
1742 // stream is not supported.
1743 nsCOMPtr<nsIInputStream> copyStream;
1744 rv = NS_NewByteInputStream(getter_AddRefs(copyStream),
1745 Span(fromRawSegment, count),
1746 NS_ASSIGNMENT_DEPEND);
1748 if (NS_SUCCEEDED(rv) && xmlHttpRequest->mXMLParserStreamListener) {
1749 NS_ASSERTION(copyStream, "NS_NewByteInputStream lied");
1750 nsresult parsingResult =
1751 xmlHttpRequest->mXMLParserStreamListener->OnDataAvailable(
1752 xmlHttpRequest->mChannel, copyStream, toOffset, count);
1754 // No use to continue parsing if we failed here, but we
1755 // should still finish reading the stream
1756 if (NS_FAILED(parsingResult)) {
1757 xmlHttpRequest->mFlagParseBody = false;
1762 if (NS_SUCCEEDED(rv)) {
1763 *writeCount = count;
1764 } else {
1765 *writeCount = 0;
1768 return rv;
1771 namespace {
1773 void GetBlobURIFromChannel(nsIRequest* aRequest, nsIURI** aURI) {
1774 MOZ_ASSERT(aRequest);
1775 MOZ_ASSERT(aURI);
1777 *aURI = nullptr;
1779 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
1780 if (!channel) {
1781 return;
1784 nsCOMPtr<nsIURI> uri;
1785 nsresult rv = channel->GetURI(getter_AddRefs(uri));
1786 if (NS_FAILED(rv)) {
1787 return;
1790 if (!dom::IsBlobURI(uri)) {
1791 return;
1794 uri.forget(aURI);
1797 nsresult GetLocalFileFromChannel(nsIRequest* aRequest, nsIFile** aFile) {
1798 MOZ_ASSERT(aRequest);
1799 MOZ_ASSERT(aFile);
1801 *aFile = nullptr;
1803 nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aRequest);
1804 if (!fc) {
1805 return NS_OK;
1808 nsCOMPtr<nsIFile> file;
1809 nsresult rv = fc->GetFile(getter_AddRefs(file));
1810 if (NS_WARN_IF(NS_FAILED(rv))) {
1811 return rv;
1814 file.forget(aFile);
1815 return NS_OK;
1818 nsresult DummyStreamReaderFunc(nsIInputStream* aInputStream, void* aClosure,
1819 const char* aFromRawSegment, uint32_t aToOffset,
1820 uint32_t aCount, uint32_t* aWriteCount) {
1821 *aWriteCount = aCount;
1822 return NS_OK;
1825 class FileCreationHandler final : public PromiseNativeHandler {
1826 public:
1827 NS_DECL_ISUPPORTS
1829 static void Create(Promise* aPromise, XMLHttpRequestMainThread* aXHR) {
1830 MOZ_ASSERT(aPromise);
1832 RefPtr<FileCreationHandler> handler = new FileCreationHandler(aXHR);
1833 aPromise->AppendNativeHandler(handler);
1836 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
1837 ErrorResult& aRv) override {
1838 if (NS_WARN_IF(!aValue.isObject())) {
1839 mXHR->LocalFileToBlobCompleted(nullptr);
1840 return;
1843 RefPtr<Blob> blob;
1844 if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) {
1845 mXHR->LocalFileToBlobCompleted(nullptr);
1846 return;
1849 mXHR->LocalFileToBlobCompleted(blob->Impl());
1852 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
1853 ErrorResult& aRv) override {
1854 mXHR->LocalFileToBlobCompleted(nullptr);
1857 private:
1858 explicit FileCreationHandler(XMLHttpRequestMainThread* aXHR) : mXHR(aXHR) {
1859 MOZ_ASSERT(aXHR);
1862 ~FileCreationHandler() = default;
1864 RefPtr<XMLHttpRequestMainThread> mXHR;
1867 NS_IMPL_ISUPPORTS0(FileCreationHandler)
1869 } // namespace
1871 void XMLHttpRequestMainThread::LocalFileToBlobCompleted(BlobImpl* aBlobImpl) {
1872 MOZ_ASSERT(mState != XMLHttpRequest_Binding::DONE);
1874 mResponseBlobImpl = aBlobImpl;
1875 mBlobStorage = nullptr;
1876 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1878 ChangeStateToDone(mFlagSyncLooping);
1881 NS_IMETHODIMP
1882 XMLHttpRequestMainThread::OnDataAvailable(nsIRequest* request,
1883 nsIInputStream* inStr,
1884 uint64_t sourceOffset,
1885 uint32_t count) {
1886 DEBUG_WORKERREFS;
1887 NS_ENSURE_ARG_POINTER(inStr);
1889 mProgressSinceLastProgressEvent = true;
1890 XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
1892 nsresult rv;
1894 if (mResponseType == XMLHttpRequestResponseType::Blob) {
1895 nsCOMPtr<nsIFile> localFile;
1896 nsCOMPtr<nsIURI> blobURI;
1897 GetBlobURIFromChannel(request, getter_AddRefs(blobURI));
1898 if (blobURI) {
1899 RefPtr<BlobImpl> blobImpl;
1900 rv = NS_GetBlobForBlobURI(blobURI, getter_AddRefs(blobImpl));
1901 if (NS_SUCCEEDED(rv)) {
1902 mResponseBlobImpl = blobImpl;
1904 } else {
1905 rv = GetLocalFileFromChannel(request, getter_AddRefs(localFile));
1907 if (NS_WARN_IF(NS_FAILED(rv))) {
1908 return rv;
1911 if (mResponseBlobImpl || localFile) {
1912 mBlobStorage = nullptr;
1913 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1915 // The nsIStreamListener contract mandates us to read from the stream
1916 // before returning.
1917 uint32_t totalRead;
1918 rv = inStr->ReadSegments(DummyStreamReaderFunc, nullptr, count,
1919 &totalRead);
1920 NS_ENSURE_SUCCESS(rv, rv);
1922 ChangeState(XMLHttpRequest_Binding::LOADING);
1924 // Cancel() must be called with an error. We use
1925 // NS_ERROR_FILE_ALREADY_EXISTS to know that we've aborted the operation
1926 // just because we can retrieve the File from the channel directly.
1927 return request->Cancel(NS_ERROR_FILE_ALREADY_EXISTS);
1931 uint32_t totalRead;
1932 rv = inStr->ReadSegments(XMLHttpRequestMainThread::StreamReaderFunc,
1933 (void*)this, count, &totalRead);
1934 NS_ENSURE_SUCCESS(rv, rv);
1936 // Fire the first progress event/loading state change
1937 if (mState == XMLHttpRequest_Binding::HEADERS_RECEIVED) {
1938 ChangeState(XMLHttpRequest_Binding::LOADING);
1939 if (!mFlagSynchronous) {
1940 DispatchProgressEvent(this, Events::progress, mLoadTransferred,
1941 mLoadTotal);
1943 mProgressSinceLastProgressEvent = false;
1946 if (!mFlagSynchronous && !mProgressTimerIsActive) {
1947 StartProgressEventTimer();
1950 return NS_OK;
1953 NS_IMETHODIMP
1954 XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) {
1955 DEBUG_WORKERREFS;
1956 AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStartRequest", NETWORK);
1958 nsresult rv = NS_OK;
1960 if (request != mChannel) {
1961 // Can this still happen?
1962 return NS_OK;
1965 // Don't do anything if we have been aborted
1966 if (mState == XMLHttpRequest_Binding::UNSENT) {
1967 return NS_OK;
1970 // Don't do anything if we're in mid-abort, but let the request
1971 // know (this can happen due to race conditions in valid XHRs,
1972 // see bz1070763 for info).
1973 if (mFlagAborted) {
1974 return NS_BINDING_ABORTED;
1977 // Don't do anything if we have timed out.
1978 if (mFlagTimedOut) {
1979 return NS_OK;
1982 // If we were asked for a bad range on a blob URL, but we're async,
1983 // we should throw now in order to fire an error progress event.
1984 if (BadContentRangeRequested()) {
1985 return NS_ERROR_NET_PARTIAL_TRANSFER;
1988 nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
1989 NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
1991 nsresult status;
1992 request->GetStatus(&status);
1993 if (mErrorLoad == ErrorType::eOK && NS_FAILED(status)) {
1994 mErrorLoad = ErrorType::eRequest;
1995 mErrorLoadDetail = status;
1998 // Upload phase is now over. If we were uploading anything,
1999 // stop the timer and fire any final progress events.
2000 if (mUpload && !mUploadComplete && mErrorLoad == ErrorType::eOK &&
2001 !mFlagSynchronous) {
2002 StopProgressEventTimer();
2004 mUploadTransferred = mUploadTotal;
2006 if (mProgressSinceLastProgressEvent) {
2007 DispatchProgressEvent(mUpload, Events::progress, mUploadTransferred,
2008 mUploadTotal);
2009 mProgressSinceLastProgressEvent = false;
2012 mUploadComplete = true;
2013 DispatchProgressEvent(mUpload, Events::load, mUploadTotal, mUploadTotal);
2016 mFlagParseBody = true;
2017 if (mErrorLoad == ErrorType::eOK) {
2018 ChangeState(XMLHttpRequest_Binding::HEADERS_RECEIVED);
2021 ResetResponse();
2023 if (!mOverrideMimeType.IsEmpty()) {
2024 channel->SetContentType(NS_ConvertUTF16toUTF8(mOverrideMimeType));
2027 // Fallback to 'application/octet-stream' (leaving data URLs alone)
2028 if (!IsBlobURI(mRequestURL)) {
2029 nsAutoCString type;
2030 channel->GetContentType(type);
2031 if (type.IsEmpty() || type.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
2032 channel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
2036 DetectCharset();
2038 // Set up arraybuffer
2039 if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
2040 NS_SUCCEEDED(status)) {
2041 if (mIsMappedArrayBuffer) {
2042 nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
2043 if (jarChannel) {
2044 nsCOMPtr<nsIURI> uri;
2045 rv = channel->GetURI(getter_AddRefs(uri));
2046 if (NS_SUCCEEDED(rv)) {
2047 nsAutoCString file;
2048 nsAutoCString scheme;
2049 uri->GetScheme(scheme);
2050 if (scheme.LowerCaseEqualsLiteral("jar")) {
2051 nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri);
2052 if (jarURI) {
2053 jarURI->GetJAREntry(file);
2056 nsCOMPtr<nsIFile> jarFile;
2057 jarChannel->GetJarFile(getter_AddRefs(jarFile));
2058 if (!jarFile) {
2059 mIsMappedArrayBuffer = false;
2060 } else {
2061 rv = mArrayBufferBuilder->MapToFileInPackage(file, jarFile);
2062 // This can happen legitimately if there are compressed files
2063 // in the jarFile. See bug #1357219. No need to warn on the error.
2064 if (NS_FAILED(rv)) {
2065 mIsMappedArrayBuffer = false;
2066 } else {
2067 channel->SetContentType("application/mem-mapped"_ns);
2073 // If memory mapping failed, mIsMappedArrayBuffer would be set to false,
2074 // and we want it fallback to the malloc way.
2075 if (!mIsMappedArrayBuffer) {
2076 int64_t contentLength;
2077 rv = channel->GetContentLength(&contentLength);
2078 if (NS_SUCCEEDED(rv) && contentLength > 0 &&
2079 contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) {
2080 mArrayBufferBuilder->SetCapacity(static_cast<int32_t>(contentLength));
2085 // Set up responseXML
2086 // Fetch spec Main Fetch step 21: ignore body for head/connect methods.
2087 bool parseBody = (mResponseType == XMLHttpRequestResponseType::_empty ||
2088 mResponseType == XMLHttpRequestResponseType::Document) &&
2089 !(mRequestMethod.EqualsLiteral("HEAD") ||
2090 mRequestMethod.EqualsLiteral("CONNECT"));
2092 if (parseBody) {
2093 // Do not try to parse documents if content-length = 0
2094 int64_t contentLength;
2095 if (NS_SUCCEEDED(mChannel->GetContentLength(&contentLength)) &&
2096 contentLength == 0) {
2097 parseBody = false;
2101 mIsHtml = false;
2102 mWarnAboutSyncHtml = false;
2103 if (parseBody && NS_SUCCEEDED(status)) {
2104 // We can gain a huge performance win by not even trying to
2105 // parse non-XML data. This also protects us from the situation
2106 // where we have an XML document and sink, but HTML (or other)
2107 // parser, which can produce unreliable results.
2108 nsAutoCString type;
2109 channel->GetContentType(type);
2111 if ((mResponseType == XMLHttpRequestResponseType::Document) &&
2112 type.EqualsLiteral("text/html")) {
2113 // HTML parsing is only supported for responseType == "document" to
2114 // avoid running the parser and, worse, populating responseXML for
2115 // legacy users of XHR who use responseType == "" for retrieving the
2116 // responseText of text/html resources. This legacy case is so common
2117 // that it's not useful to emit a warning about it.
2118 if (mFlagSynchronous) {
2119 // We don't make cool new features available in the bad synchronous
2120 // mode. The synchronous mode is for legacy only.
2121 mWarnAboutSyncHtml = true;
2122 mFlagParseBody = false;
2123 } else {
2124 mIsHtml = true;
2126 } else if (!type.IsEmpty() && (!(type.EqualsLiteral("text/xml") ||
2127 type.EqualsLiteral("application/xml") ||
2128 StringEndsWith(type, "+xml"_ns)))) {
2129 // Follow https://xhr.spec.whatwg.org/
2130 // If final MIME type is not null, text/html, text/xml, application/xml,
2131 // or does not end in +xml, return null.
2132 mFlagParseBody = false;
2134 } else {
2135 // The request failed, so we shouldn't be parsing anyway
2136 mFlagParseBody = false;
2139 if (mFlagParseBody) {
2140 nsCOMPtr<nsIURI> baseURI, docURI;
2141 rv = mChannel->GetURI(getter_AddRefs(docURI));
2142 NS_ENSURE_SUCCESS(rv, rv);
2143 baseURI = docURI;
2145 nsCOMPtr<Document> doc = GetDocumentIfCurrent();
2146 nsCOMPtr<nsIURI> chromeXHRDocURI, chromeXHRDocBaseURI;
2147 if (doc) {
2148 chromeXHRDocURI = doc->GetDocumentURI();
2149 chromeXHRDocBaseURI = doc->GetBaseURI();
2150 } else {
2151 // If we're no longer current, just kill the load, though it really should
2152 // have been killed already.
2153 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
2154 return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
2158 // Create an empty document from it.
2159 const auto& emptyStr = u""_ns;
2160 nsIGlobalObject* global = DOMEventTargetHelper::GetParentObject();
2162 nsCOMPtr<nsIPrincipal> requestingPrincipal;
2163 rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
2164 channel, getter_AddRefs(requestingPrincipal));
2165 NS_ENSURE_SUCCESS(rv, rv);
2167 rv = NS_NewDOMDocument(
2168 getter_AddRefs(mResponseXML), emptyStr, emptyStr, nullptr, docURI,
2169 baseURI, requestingPrincipal, true, global,
2170 mIsHtml ? DocumentFlavorHTML : DocumentFlavorLegacyGuess);
2171 NS_ENSURE_SUCCESS(rv, rv);
2172 mResponseXML->SetChromeXHRDocURI(chromeXHRDocURI);
2173 mResponseXML->SetChromeXHRDocBaseURI(chromeXHRDocBaseURI);
2175 // suppress parsing failure messages to console for statuses which
2176 // can have empty bodies (see bug 884693).
2177 IgnoredErrorResult rv2;
2178 uint32_t responseStatus = GetStatus(rv2);
2179 if (!rv2.Failed() && (responseStatus == 201 || responseStatus == 202 ||
2180 responseStatus == 204 || responseStatus == 205 ||
2181 responseStatus == 304)) {
2182 mResponseXML->SetSuppressParserErrorConsoleMessages(true);
2185 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2186 bool isCrossSite = false;
2187 isCrossSite = loadInfo->GetTainting() != LoadTainting::Basic;
2189 if (isCrossSite) {
2190 mResponseXML->DisableCookieAccess();
2193 nsCOMPtr<nsIStreamListener> listener;
2194 nsCOMPtr<nsILoadGroup> loadGroup;
2195 channel->GetLoadGroup(getter_AddRefs(loadGroup));
2197 // suppress <parsererror> nodes on XML document parse failure, but only
2198 // for non-privileged code (including Web Extensions). See bug 289714.
2199 if (!IsSystemXHR()) {
2200 mResponseXML->SetSuppressParserErrorElement(true);
2203 rv = mResponseXML->StartDocumentLoad(kLoadAsData, channel, loadGroup,
2204 nullptr, getter_AddRefs(listener),
2205 !isCrossSite);
2206 NS_ENSURE_SUCCESS(rv, rv);
2208 // the spec requires the response document.referrer to be the empty string
2209 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2210 new ReferrerInfo(nullptr, mResponseXML->ReferrerPolicy());
2211 mResponseXML->SetReferrerInfo(referrerInfo);
2213 mXMLParserStreamListener = listener;
2214 rv = mXMLParserStreamListener->OnStartRequest(request);
2215 NS_ENSURE_SUCCESS(rv, rv);
2218 // Download phase beginning; start the progress event timer if necessary.
2219 if (NS_SUCCEEDED(rv) && HasListenersFor(nsGkAtoms::onprogress)) {
2220 StartProgressEventTimer();
2223 return NS_OK;
2226 NS_IMETHODIMP
2227 XMLHttpRequestMainThread::OnStopRequest(nsIRequest* request, nsresult status) {
2228 DEBUG_WORKERREFS;
2229 AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStopRequest", NETWORK);
2231 if (request != mChannel) {
2232 // Can this still happen?
2233 return NS_OK;
2236 // Send the decoder the signal that we've hit the end of the stream,
2237 // but only when decoding text eagerly.
2238 if (mDecoder && ((mResponseType == XMLHttpRequestResponseType::Text) ||
2239 (mResponseType == XMLHttpRequestResponseType::Json) ||
2240 (mResponseType == XMLHttpRequestResponseType::_empty &&
2241 !mResponseXML))) {
2242 AppendToResponseText(Span<const uint8_t>(), true);
2245 mWaitingForOnStopRequest = false;
2247 // make sure to notify the listener if we were aborted
2248 // XXX in fact, why don't we do the cleanup below in this case??
2249 // UNSENT is for abort calls. See OnStartRequest above.
2250 if (mState == XMLHttpRequest_Binding::UNSENT || mFlagTimedOut) {
2251 if (mXMLParserStreamListener)
2252 (void)mXMLParserStreamListener->OnStopRequest(request, status);
2253 return NS_OK;
2256 // Is this good enough here?
2257 if (mXMLParserStreamListener && mFlagParseBody) {
2258 mXMLParserStreamListener->OnStopRequest(request, status);
2261 mXMLParserStreamListener = nullptr;
2262 mContext = nullptr;
2264 // If window.stop() or other aborts were issued, handle as an abort
2265 if (status == NS_BINDING_ABORTED) {
2266 mFlagParseBody = false;
2267 IgnoredErrorResult rv;
2268 RequestErrorSteps(Events::abort, NS_ERROR_DOM_ABORT_ERR, rv);
2269 ChangeState(XMLHttpRequest_Binding::UNSENT, false);
2270 return NS_OK;
2273 // If we were just reading a blob URL, we're already done
2274 if (status == NS_ERROR_FILE_ALREADY_EXISTS && mResponseBlobImpl) {
2275 ChangeStateToDone(mFlagSyncLooping);
2276 return NS_OK;
2279 bool waitingForBlobCreation = false;
2281 // If we have this error, we have to deal with a file: URL + responseType =
2282 // blob. We have this error because we canceled the channel. The status will
2283 // be set to NS_OK.
2284 if (!mResponseBlobImpl && status == NS_ERROR_FILE_ALREADY_EXISTS &&
2285 mResponseType == XMLHttpRequestResponseType::Blob) {
2286 nsCOMPtr<nsIFile> file;
2287 nsresult rv = GetLocalFileFromChannel(request, getter_AddRefs(file));
2288 if (NS_WARN_IF(NS_FAILED(rv))) {
2289 return rv;
2292 if (file) {
2293 nsAutoCString contentType;
2294 rv = mChannel->GetContentType(contentType);
2295 if (NS_WARN_IF(NS_FAILED(rv))) {
2296 return rv;
2299 ChromeFilePropertyBag bag;
2300 CopyUTF8toUTF16(contentType, bag.mType);
2302 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
2304 ErrorResult error;
2305 RefPtr<Promise> promise =
2306 FileCreatorHelper::CreateFile(global, file, bag, true, error);
2307 if (NS_WARN_IF(error.Failed())) {
2308 return error.StealNSResult();
2311 FileCreationHandler::Create(promise, this);
2312 waitingForBlobCreation = true;
2313 status = NS_OK;
2315 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
2316 NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
2320 if (NS_SUCCEEDED(status) &&
2321 mResponseType == XMLHttpRequestResponseType::Blob &&
2322 !waitingForBlobCreation) {
2323 // Smaller files may be written in cache map instead of separate files.
2324 // Also, no-store response cannot be written in persistent cache.
2325 nsAutoCString contentType;
2326 if (!mOverrideMimeType.IsEmpty()) {
2327 contentType.Assign(NS_ConvertUTF16toUTF8(mOverrideMimeType));
2328 } else {
2329 mChannel->GetContentType(contentType);
2332 // mBlobStorage can be null if the channel is non-file non-cacheable
2333 // and if the response length is zero.
2334 MaybeCreateBlobStorage();
2335 mBlobStorage->GetBlobImplWhenReady(contentType, this);
2336 waitingForBlobCreation = true;
2338 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
2339 NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
2340 } else if (NS_SUCCEEDED(status) && !mIsMappedArrayBuffer &&
2341 mResponseType == XMLHttpRequestResponseType::Arraybuffer) {
2342 // set the capacity down to the actual length, to realloc back
2343 // down to the actual size
2344 if (!mArrayBufferBuilder->SetCapacity(mArrayBufferBuilder->Length())) {
2345 // this should never happen!
2346 status = NS_ERROR_UNEXPECTED;
2350 nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
2351 NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
2353 channel->SetNotificationCallbacks(nullptr);
2354 mNotificationCallbacks = nullptr;
2355 mChannelEventSink = nullptr;
2356 mProgressEventSink = nullptr;
2358 bool wasSync = mFlagSyncLooping;
2359 mFlagSyncLooping = false;
2360 mRequestSentTime = 0;
2362 // update our charset and decoder to match mResponseXML,
2363 // before it is possibly nulled out
2364 MatchCharsetAndDecoderToResponseDocument();
2366 if (NS_FAILED(status)) {
2367 // This can happen if the server is unreachable. Other possible
2368 // reasons are that the user leaves the page or hits the ESC key.
2370 mErrorLoad = ErrorType::eUnreachable;
2371 mErrorLoadDetail = status;
2372 mResponseXML = nullptr;
2374 // Handle network errors specifically per spec.
2375 if (NS_ERROR_GET_MODULE(status) == NS_ERROR_MODULE_NETWORK) {
2376 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
2377 ("%p detected networking error 0x%" PRIx32 "\n", this,
2378 static_cast<uint32_t>(status)));
2379 IgnoredErrorResult rv;
2380 mFlagParseBody = false;
2381 RequestErrorSteps(Events::error, NS_ERROR_DOM_NETWORK_ERR, rv);
2382 // RequestErrorSteps will not call ChangeStateToDone for sync XHRs, so we
2383 // do so here to ensure progress events are sent and our state is sane.
2384 if (mFlagSynchronous) {
2385 ChangeStateToDone(wasSync);
2387 return NS_OK;
2390 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
2391 ("%p detected unreachable error 0x%" PRIx32 "\n", this,
2392 static_cast<uint32_t>(status)));
2395 // If we're uninitialized at this point, we encountered an error
2396 // earlier and listeners have already been notified. Also we do
2397 // not want to do this if we already completed.
2398 if (mState == XMLHttpRequest_Binding::UNSENT ||
2399 mState == XMLHttpRequest_Binding::DONE) {
2400 return NS_OK;
2403 if (!mResponseXML) {
2404 mFlagParseBody = false;
2406 // We postpone the 'done' until the creation of the Blob is completed.
2407 if (!waitingForBlobCreation) {
2408 ChangeStateToDone(wasSync);
2411 return NS_OK;
2414 if (mIsHtml) {
2415 NS_ASSERTION(!mFlagSyncLooping,
2416 "We weren't supposed to support HTML parsing with XHR!");
2417 mParseEndListener = new nsXHRParseEndListener(this);
2418 RefPtr<EventTarget> eventTarget = mResponseXML;
2419 EventListenerManager* manager = eventTarget->GetOrCreateListenerManager();
2420 manager->AddEventListenerByType(mParseEndListener,
2421 kLiteralString_DOMContentLoaded,
2422 TrustedEventsAtSystemGroupBubble());
2423 return NS_OK;
2424 } else {
2425 mFlagParseBody = false;
2428 // We might have been sent non-XML data. If that was the case,
2429 // we should null out the document member. The idea in this
2430 // check here is that if there is no document element it is not
2431 // an XML document. We might need a fancier check...
2432 if (!mResponseXML->GetRootElement()) {
2433 mErrorParsingXML = true;
2434 mResponseXML = nullptr;
2436 ChangeStateToDone(wasSync);
2437 return NS_OK;
2440 void XMLHttpRequestMainThread::OnBodyParseEnd() {
2441 mFlagParseBody = false;
2442 mParseEndListener = nullptr;
2443 ChangeStateToDone(mFlagSyncLooping);
2446 void XMLHttpRequestMainThread::MatchCharsetAndDecoderToResponseDocument() {
2447 if (mResponseXML &&
2448 (!mDecoder ||
2449 mDecoder->Encoding() != mResponseXML->GetDocumentCharacterSet())) {
2450 TruncateResponseText();
2451 mResponseBodyDecodedPos = 0;
2452 mEofDecoded = false;
2453 mDecoder = mResponseXML->GetDocumentCharacterSet()->NewDecoder();
2457 void XMLHttpRequestMainThread::DisconnectDoneNotifier() {
2458 if (mDelayedDoneNotifier) {
2459 // Disconnect may release the last reference to 'this'.
2460 RefPtr<XMLHttpRequestMainThread> kungfuDeathGrip = this;
2461 mDelayedDoneNotifier->Disconnect();
2462 mDelayedDoneNotifier = nullptr;
2466 void XMLHttpRequestMainThread::ChangeStateToDone(bool aWasSync) {
2467 DEBUG_WORKERREFS;
2468 DisconnectDoneNotifier();
2470 if (!mForWorker && !aWasSync && mChannel) {
2471 // If the top level page is loading, try to postpone the handling of the
2472 // final events.
2473 nsLoadFlags loadFlags = 0;
2474 mChannel->GetLoadFlags(&loadFlags);
2475 if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
2476 nsPIDOMWindowInner* owner = GetOwner();
2477 BrowsingContext* bc = owner ? owner->GetBrowsingContext() : nullptr;
2478 bc = bc ? bc->Top() : nullptr;
2479 if (bc && bc->IsLoading()) {
2480 MOZ_ASSERT(!mDelayedDoneNotifier);
2481 RefPtr<XMLHttpRequestDoneNotifier> notifier =
2482 new XMLHttpRequestDoneNotifier(this);
2483 mDelayedDoneNotifier = notifier;
2484 bc->AddDeprioritizedLoadRunner(notifier);
2485 return;
2490 ChangeStateToDoneInternal();
2493 void XMLHttpRequestMainThread::ChangeStateToDoneInternal() {
2494 DEBUG_WORKERREFS;
2495 DisconnectDoneNotifier();
2496 StopProgressEventTimer();
2498 MOZ_ASSERT(!mFlagParseBody,
2499 "ChangeStateToDone() called before async HTML parsing is done.");
2501 mFlagSend = false;
2503 CancelTimeoutTimer();
2505 // Per spec, fire the last download progress event, if any,
2506 // before readystatechange=4/done. (Note that 0-sized responses
2507 // will have not sent a progress event yet, so one must be sent here).
2508 if (!mFlagSynchronous &&
2509 (!mLoadTransferred || mProgressSinceLastProgressEvent)) {
2510 DispatchProgressEvent(this, Events::progress, mLoadTransferred, mLoadTotal);
2511 mProgressSinceLastProgressEvent = false;
2514 // Notify the document when an XHR request completes successfully.
2515 // This is used by the password manager as a hint to observe DOM mutations.
2516 // Call this prior to changing state to DONE to ensure we set up the
2517 // observer before mutations occur.
2518 if (mErrorLoad == ErrorType::eOK) {
2519 Document* doc = GetDocumentIfCurrent();
2520 if (doc) {
2521 doc->NotifyFetchOrXHRSuccess();
2525 // Per spec, fire readystatechange=4/done before final error events.
2526 ChangeState(XMLHttpRequest_Binding::DONE, true);
2528 // Per spec, if we failed in the upload phase, fire a final error
2529 // and loadend events for the upload after readystatechange=4/done.
2530 if (!mFlagSynchronous && mUpload && !mUploadComplete) {
2531 DispatchProgressEvent(mUpload, Events::error, 0, -1);
2534 // Per spec, fire download's load/error and loadend events after
2535 // readystatechange=4/done (and of course all upload events).
2536 if (mErrorLoad != ErrorType::eOK) {
2537 DispatchProgressEvent(this, Events::error, 0, -1);
2538 } else {
2539 DispatchProgressEvent(this, Events::load, mLoadTransferred, mLoadTotal);
2542 if (mErrorLoad != ErrorType::eOK) {
2543 // By nulling out channel here we make it so that Send() can test
2544 // for that and throw. Also calling the various status
2545 // methods/members will not throw.
2546 // This matches what IE does.
2547 mChannel = nullptr;
2551 nsresult XMLHttpRequestMainThread::CreateChannel() {
2552 DEBUG_WORKERREFS;
2553 // When we are called from JS we can find the load group for the page,
2554 // and add ourselves to it. This way any pending requests
2555 // will be automatically aborted if the user leaves the page.
2556 nsCOMPtr<nsILoadGroup> loadGroup = GetLoadGroup();
2558 nsSecurityFlags secFlags;
2559 nsLoadFlags loadFlags = nsIRequest::LOAD_BACKGROUND;
2560 uint32_t sandboxFlags = 0;
2561 if (mPrincipal->IsSystemPrincipal()) {
2562 // When chrome is loading we want to make sure to sandbox any potential
2563 // result document. We also want to allow cross-origin loads.
2564 secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
2565 sandboxFlags = SANDBOXED_ORIGIN;
2566 } else if (IsSystemXHR()) {
2567 // For pages that have appropriate permissions, we want to still allow
2568 // cross-origin loads, but make sure that the any potential result
2569 // documents get the same principal as the loader.
2570 secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
2571 nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
2572 loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
2573 } else {
2574 // Otherwise use CORS. Again, make sure that potential result documents
2575 // use the same principal as the loader.
2576 secFlags = nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT |
2577 nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
2580 if (mIsAnon) {
2581 secFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
2584 // Use the responsibleDocument if we have it, except for dedicated workers
2585 // where it will be the parent document, which is not the one we want to use.
2586 nsresult rv;
2587 nsCOMPtr<Document> responsibleDocument = GetDocumentIfCurrent();
2588 if (responsibleDocument &&
2589 responsibleDocument->NodePrincipal() == mPrincipal) {
2590 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL,
2591 responsibleDocument, secFlags,
2592 nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2593 nullptr, // aPerformanceStorage
2594 loadGroup,
2595 nullptr, // aCallbacks
2596 loadFlags, nullptr, sandboxFlags);
2597 } else if (mClientInfo.isSome()) {
2598 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
2599 mClientInfo.ref(), mController, secFlags,
2600 nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2601 mCookieJarSettings,
2602 mPerformanceStorage, // aPerformanceStorage
2603 loadGroup,
2604 nullptr, // aCallbacks
2605 loadFlags, nullptr, sandboxFlags);
2606 } else {
2607 // Otherwise use the principal.
2608 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
2609 secFlags, nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2610 mCookieJarSettings,
2611 mPerformanceStorage, // aPerformanceStorage
2612 loadGroup,
2613 nullptr, // aCallbacks
2614 loadFlags, nullptr, sandboxFlags);
2616 NS_ENSURE_SUCCESS(rv, rv);
2618 if (mCSPEventListener) {
2619 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2620 rv = loadInfo->SetCspEventListener(mCSPEventListener);
2621 NS_ENSURE_SUCCESS(rv, rv);
2624 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2625 if (httpChannel) {
2626 rv = httpChannel->SetRequestMethod(mRequestMethod);
2627 NS_ENSURE_SUCCESS(rv, rv);
2629 httpChannel->SetSource(profiler_capture_backtrace());
2631 // Set the initiator type
2632 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
2633 if (timedChannel) {
2634 timedChannel->SetInitiatorType(u"xmlhttprequest"_ns);
2638 return NS_OK;
2641 void XMLHttpRequestMainThread::MaybeLowerChannelPriority() {
2642 nsCOMPtr<Document> doc = GetDocumentIfCurrent();
2643 if (!doc) {
2644 return;
2647 AutoJSAPI jsapi;
2648 if (!jsapi.Init(GetOwnerGlobal())) {
2649 return;
2652 JSContext* cx = jsapi.cx();
2654 if (!doc->IsScriptTracking(cx)) {
2655 return;
2658 if (StaticPrefs::network_http_tailing_enabled()) {
2659 nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel);
2660 if (cos) {
2661 // Adding TailAllowed to overrule the Unblocked flag, but to preserve
2662 // the effect of Unblocked when tailing is off.
2663 cos->AddClassFlags(nsIClassOfService::Throttleable |
2664 nsIClassOfService::Tail |
2665 nsIClassOfService::TailAllowed);
2669 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
2670 if (p) {
2671 p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
2675 nsresult XMLHttpRequestMainThread::InitiateFetch(
2676 already_AddRefed<nsIInputStream> aUploadStream, int64_t aUploadLength,
2677 nsACString& aUploadContentType) {
2678 DEBUG_WORKERREFS;
2679 nsresult rv;
2680 nsCOMPtr<nsIInputStream> uploadStream = std::move(aUploadStream);
2682 if (!uploadStream) {
2683 RefPtr<PreloaderBase> preload = FindPreload();
2684 if (preload) {
2685 // Because of bug 682305, we can't let listener be the XHR object itself
2686 // because JS wouldn't be able to use it. So create a listener around
2687 // 'this'. Make sure to hold a strong reference so that we don't leak the
2688 // wrapper.
2689 nsCOMPtr<nsIStreamListener> listener =
2690 new net::nsStreamListenerWrapper(this);
2691 rv = preload->AsyncConsume(listener);
2692 if (NS_SUCCEEDED(rv)) {
2693 mFromPreload = true;
2695 // May be null when the preload has already finished, but the XHR code
2696 // is safe to live with it.
2697 mChannel = preload->Channel();
2698 MOZ_ASSERT(mChannel);
2699 EnsureChannelContentType();
2700 return NS_OK;
2703 preload = nullptr;
2707 // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which
2708 // in turn keeps STOP button from becoming active. If the consumer passed in
2709 // a progress event handler we must load with nsIRequest::LOAD_NORMAL or
2710 // necko won't generate any progress notifications.
2711 if (HasListenersFor(nsGkAtoms::onprogress) ||
2712 (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress))) {
2713 nsLoadFlags loadFlags;
2714 mChannel->GetLoadFlags(&loadFlags);
2715 loadFlags &= ~nsIRequest::LOAD_BACKGROUND;
2716 loadFlags |= nsIRequest::LOAD_NORMAL;
2717 mChannel->SetLoadFlags(loadFlags);
2720 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2721 if (httpChannel) {
2722 // If the user hasn't overridden the Accept header, set it to */* per spec.
2723 if (!mAuthorRequestHeaders.Has("accept")) {
2724 mAuthorRequestHeaders.Set("accept", "*/*"_ns);
2727 mAuthorRequestHeaders.ApplyToChannel(httpChannel, false, false);
2729 if (!IsSystemXHR()) {
2730 nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
2731 nsCOMPtr<Document> doc = owner ? owner->GetExtantDoc() : nullptr;
2732 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2733 ReferrerInfo::CreateForFetch(mPrincipal, doc);
2734 Unused << httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
2737 // Some extensions override the http protocol handler and provide their own
2738 // implementation. The channels returned from that implementation don't
2739 // always seem to implement the nsIUploadChannel2 interface, presumably
2740 // because it's a new interface. Eventually we should remove this and simply
2741 // require that http channels implement the new interface (see bug 529041).
2742 nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
2743 if (!uploadChannel2) {
2744 nsCOMPtr<nsIConsoleService> consoleService =
2745 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
2746 if (consoleService) {
2747 consoleService->LogStringMessage(
2748 u"Http channel implementation doesn't support nsIUploadChannel2. "
2749 "An extension has supplied a non-functional http protocol handler. "
2750 "This will break behavior and in future releases not work at all.");
2754 if (uploadStream) {
2755 // If necessary, wrap the stream in a buffered stream so as to guarantee
2756 // support for our upload when calling ExplicitSetUploadStream.
2757 if (!NS_InputStreamIsBuffered(uploadStream)) {
2758 nsCOMPtr<nsIInputStream> bufferedStream;
2759 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
2760 uploadStream.forget(), 4096);
2761 NS_ENSURE_SUCCESS(rv, rv);
2763 uploadStream = bufferedStream;
2766 // We want to use a newer version of the upload channel that won't
2767 // ignore the necessary headers for an empty Content-Type.
2768 nsCOMPtr<nsIUploadChannel2> uploadChannel2(
2769 do_QueryInterface(httpChannel));
2770 // This assertion will fire if buggy extensions are installed
2771 NS_ASSERTION(uploadChannel2, "http must support nsIUploadChannel2");
2772 if (uploadChannel2) {
2773 uploadChannel2->ExplicitSetUploadStream(
2774 uploadStream, aUploadContentType, mUploadTotal, mRequestMethod,
2775 false);
2776 } else {
2777 // The http channel doesn't support the new nsIUploadChannel2.
2778 // Emulate it as best we can using nsIUploadChannel.
2779 if (aUploadContentType.IsEmpty()) {
2780 aUploadContentType.AssignLiteral("application/octet-stream");
2782 nsCOMPtr<nsIUploadChannel> uploadChannel =
2783 do_QueryInterface(httpChannel);
2784 uploadChannel->SetUploadStream(uploadStream, aUploadContentType,
2785 mUploadTotal);
2786 // Reset the method to its original value
2787 rv = httpChannel->SetRequestMethod(mRequestMethod);
2788 MOZ_ASSERT(NS_SUCCEEDED(rv));
2793 // Should set a Content-Range header for blob scheme, and also slice the
2794 // blob appropriately, so we process the Range header here for later use.
2795 if (IsBlobURI(mRequestURL)) {
2796 nsAutoCString range;
2797 mAuthorRequestHeaders.Get("range", range);
2798 if (!range.IsVoid()) {
2799 rv = NS_SetChannelContentRangeForBlobURI(mChannel, mRequestURL, range);
2800 if (mFlagSynchronous && NS_FAILED(rv)) {
2801 // We later fire an error progress event for non-sync
2802 mState = XMLHttpRequest_Binding::DONE;
2803 return NS_ERROR_DOM_NETWORK_ERR;
2808 // Due to the chrome-only XHR.channel API, we need a hacky way to set the
2809 // SEC_COOKIES_INCLUDE *after* the channel has been has been created, since
2810 // .withCredentials can be called after open() is called.
2811 // Not doing this for privileged system XHRs since those don't use CORS.
2812 if (!IsSystemXHR() && !mIsAnon && mFlagACwithCredentials) {
2813 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2814 static_cast<net::LoadInfo*>(loadInfo.get())->SetIncludeCookiesSecFlag();
2817 // We never let XHR be blocked by head CSS/JS loads to avoid potential
2818 // deadlock where server generation of CSS/JS requires an XHR signal.
2819 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
2820 if (cos) {
2821 cos->AddClassFlags(nsIClassOfService::Unblocked);
2823 // Mark channel as urgent-start if the XHR is triggered by user input
2824 // events.
2825 if (UserActivation::IsHandlingUserInput()) {
2826 cos->AddClassFlags(nsIClassOfService::UrgentStart);
2830 // Disable Necko-internal response timeouts.
2831 nsCOMPtr<nsIHttpChannelInternal> internalHttpChannel(
2832 do_QueryInterface(mChannel));
2833 if (internalHttpChannel) {
2834 rv = internalHttpChannel->SetResponseTimeoutEnabled(false);
2835 MOZ_ASSERT(NS_SUCCEEDED(rv));
2838 if (!mIsAnon) {
2839 AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS);
2842 // Bypass the network cache in cases where it makes no sense:
2843 // POST responses are always unique, and we provide no API that would
2844 // allow our consumers to specify a "cache key" to access old POST
2845 // responses, so they are not worth caching.
2846 if (mRequestMethod.EqualsLiteral("POST")) {
2847 AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE |
2848 nsIRequest::INHIBIT_CACHING);
2849 } else {
2850 // When we are sync loading, we need to bypass the local cache when it would
2851 // otherwise block us waiting for exclusive access to the cache. If we
2852 // don't do this, then we could dead lock in some cases (see bug 309424).
2854 // Also don't block on the cache entry on async if it is busy - favoring
2855 // parallelism over cache hit rate for xhr. This does not disable the cache
2856 // everywhere - only in cases where more than one channel for the same URI
2857 // is accessed simultanously.
2858 AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
2861 EnsureChannelContentType();
2863 // Set up the preflight if needed
2864 if (!IsSystemXHR()) {
2865 nsTArray<nsCString> CORSUnsafeHeaders;
2866 mAuthorRequestHeaders.GetCORSUnsafeHeaders(CORSUnsafeHeaders);
2867 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2868 loadInfo->SetCorsPreflightInfo(CORSUnsafeHeaders,
2869 mFlagHadUploadListenersOnSend);
2872 // Hook us up to listen to redirects and the like. Only do this very late
2873 // since this creates a cycle between the channel and us. This cycle has
2874 // to be manually broken if anything below fails.
2875 mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
2876 mChannel->SetNotificationCallbacks(this);
2878 if (internalHttpChannel) {
2879 internalHttpChannel->SetBlockAuthPrompt(ShouldBlockAuthPrompt());
2882 // Because of bug 682305, we can't let listener be the XHR object itself
2883 // because JS wouldn't be able to use it. So create a listener around 'this'.
2884 // Make sure to hold a strong reference so that we don't leak the wrapper.
2885 nsCOMPtr<nsIStreamListener> listener = new net::nsStreamListenerWrapper(this);
2887 // Check if this XHR is created from a tracking script.
2888 // If yes, lower the channel's priority.
2889 if (StaticPrefs::privacy_trackingprotection_lower_network_priority()) {
2890 MaybeLowerChannelPriority();
2893 // Associate any originating stack with the channel.
2894 NotifyNetworkMonitorAlternateStack(mChannel, std::move(mOriginStack));
2896 // Start reading from the channel
2897 rv = mChannel->AsyncOpen(listener);
2898 listener = nullptr;
2899 if (NS_WARN_IF(NS_FAILED(rv))) {
2900 // Drop our ref to the channel to avoid cycles. Also drop channel's
2901 // ref to us to be extra safe.
2902 mChannel->SetNotificationCallbacks(mNotificationCallbacks);
2903 mChannel = nullptr;
2905 mErrorLoad = ErrorType::eChannelOpen;
2906 mErrorLoadDetail = rv;
2908 // Per spec, we throw on sync errors, but not async.
2909 if (mFlagSynchronous) {
2910 mState = XMLHttpRequest_Binding::DONE;
2911 return NS_ERROR_DOM_NETWORK_ERR;
2915 return NS_OK;
2918 already_AddRefed<PreloaderBase> XMLHttpRequestMainThread::FindPreload() {
2919 Document* doc = GetDocumentIfCurrent();
2920 if (!doc) {
2921 return nullptr;
2923 if (mPrincipal->IsSystemPrincipal() || IsSystemXHR()) {
2924 return nullptr;
2926 if (!mRequestMethod.EqualsLiteral("GET")) {
2927 // Preload can only do GET.
2928 return nullptr;
2930 if (!mAuthorRequestHeaders.IsEmpty()) {
2931 // Preload can't set headers.
2932 return nullptr;
2935 // mIsAnon overrules mFlagACwithCredentials.
2936 CORSMode cors = (mIsAnon || !mFlagACwithCredentials)
2937 ? CORSMode::CORS_ANONYMOUS
2938 : CORSMode::CORS_USE_CREDENTIALS;
2939 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2940 ReferrerInfo::CreateForFetch(mPrincipal, doc);
2941 auto key = PreloadHashKey::CreateAsFetch(mRequestURL, cors);
2942 RefPtr<PreloaderBase> preload = doc->Preloads().LookupPreload(key);
2943 if (!preload) {
2944 return nullptr;
2947 preload->RemoveSelf(doc);
2948 preload->NotifyUsage(doc, PreloaderBase::LoadBackground::Keep);
2950 return preload.forget();
2953 void XMLHttpRequestMainThread::EnsureChannelContentType() {
2954 MOZ_ASSERT(mChannel);
2956 // We don't mess with the content type of a blob URL.
2957 if (IsBlobURI(mRequestURL)) {
2958 return;
2961 // Since we expect XML data, set the type hint accordingly
2962 // if the channel doesn't know any content type.
2963 // This means that we always try to parse local files as XML
2964 // ignoring return value, as this is not critical. Use text/xml as fallback
2965 // MIME type.
2966 nsAutoCString contentType;
2967 if (NS_FAILED(mChannel->GetContentType(contentType)) ||
2968 contentType.IsEmpty() ||
2969 contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
2970 mChannel->SetContentType("text/xml"_ns);
2974 void XMLHttpRequestMainThread::ResumeTimeout() {
2975 DEBUG_WORKERREFS;
2976 MOZ_ASSERT(NS_IsMainThread());
2977 MOZ_ASSERT(mFlagSynchronous);
2979 if (mResumeTimeoutRunnable) {
2980 DispatchToMainThread(mResumeTimeoutRunnable.forget());
2981 mResumeTimeoutRunnable = nullptr;
2985 void XMLHttpRequestMainThread::Send(
2986 const Nullable<
2987 DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString>&
2988 aData,
2989 ErrorResult& aRv) {
2990 DEBUG_WORKERREFS1(mRequestURL);
2991 NOT_CALLABLE_IN_SYNC_SEND_RV
2993 if (!CanSend(aRv)) {
2994 return;
2997 if (aData.IsNull()) {
2998 SendInternal(nullptr, false, aRv);
2999 return;
3002 if (aData.Value().IsDocument()) {
3003 BodyExtractor<Document> body(&aData.Value().GetAsDocument());
3004 SendInternal(&body, true, aRv);
3005 return;
3008 if (aData.Value().IsBlob()) {
3009 BodyExtractor<const Blob> body(&aData.Value().GetAsBlob());
3010 SendInternal(&body, false, aRv);
3011 return;
3014 if (aData.Value().IsArrayBuffer()) {
3015 BodyExtractor<const ArrayBuffer> body(&aData.Value().GetAsArrayBuffer());
3016 SendInternal(&body, false, aRv);
3017 return;
3020 if (aData.Value().IsArrayBufferView()) {
3021 BodyExtractor<const ArrayBufferView> body(
3022 &aData.Value().GetAsArrayBufferView());
3023 SendInternal(&body, false, aRv);
3024 return;
3027 if (aData.Value().IsFormData()) {
3028 BodyExtractor<const FormData> body(&aData.Value().GetAsFormData());
3029 SendInternal(&body, false, aRv);
3030 return;
3033 if (aData.Value().IsURLSearchParams()) {
3034 BodyExtractor<const URLSearchParams> body(
3035 &aData.Value().GetAsURLSearchParams());
3036 SendInternal(&body, false, aRv);
3037 return;
3040 if (aData.Value().IsUSVString()) {
3041 BodyExtractor<const nsAString> body(&aData.Value().GetAsUSVString());
3042 SendInternal(&body, true, aRv);
3043 return;
3047 nsresult XMLHttpRequestMainThread::MaybeSilentSendFailure(nsresult aRv) {
3048 // Per spec, silently fail on async request failures; throw for sync.
3049 if (mFlagSynchronous) {
3050 mState = XMLHttpRequest_Binding::DONE;
3051 return NS_ERROR_DOM_NETWORK_ERR;
3054 // Defer the actual sending of async events just in case listeners
3055 // are attached after the send() method is called.
3056 Unused << NS_WARN_IF(
3057 NS_FAILED(DispatchToMainThread(NewRunnableMethod<ErrorProgressEventType>(
3058 "dom::XMLHttpRequestMainThread::CloseRequestWithError", this,
3059 &XMLHttpRequestMainThread::CloseRequestWithError, Events::error))));
3060 return NS_OK;
3063 bool XMLHttpRequestMainThread::CanSend(ErrorResult& aRv) {
3064 if (!mPrincipal) {
3065 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
3066 return false;
3069 // Step 1
3070 if (mState != XMLHttpRequest_Binding::OPENED) {
3071 aRv.ThrowInvalidStateError("XMLHttpRequest state must be OPENED.");
3072 return false;
3075 // Step 2
3076 if (mFlagSend) {
3077 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3078 return false;
3081 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
3082 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
3083 return false;
3086 return true;
3089 void XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody,
3090 bool aBodyIsDocumentOrString,
3091 ErrorResult& aRv) {
3092 DEBUG_WORKERREFS;
3093 MOZ_ASSERT(NS_IsMainThread());
3095 // We expect that CanSend has been called before we get here!
3096 // We cannot move the remaining two checks below there because
3097 // MaybeSilentSendFailure can cause unexpected side effects if called
3098 // too early.
3100 // If open() failed to create the channel, then throw a network error
3101 // as per spec. We really should create the channel here in send(), but
3102 // we have internal code relying on the channel being created in open().
3103 if (!mChannel) {
3104 mErrorLoad = ErrorType::eChannelOpen;
3105 mErrorLoadDetail = NS_ERROR_DOM_NETWORK_ERR;
3106 mFlagSend = true; // so CloseRequestWithError sets us to DONE.
3107 aRv = MaybeSilentSendFailure(mErrorLoadDetail);
3108 return;
3111 // non-GET requests aren't allowed for blob.
3112 if (IsBlobURI(mRequestURL) && !mRequestMethod.EqualsLiteral("GET")) {
3113 mErrorLoad = ErrorType::eChannelOpen;
3114 mErrorLoadDetail = NS_ERROR_DOM_NETWORK_ERR;
3115 mFlagSend = true; // so CloseRequestWithError sets us to DONE.
3116 aRv = MaybeSilentSendFailure(mErrorLoadDetail);
3117 return;
3120 // XXX We should probably send a warning to the JS console
3121 // if there are no event listeners set and we are doing
3122 // an asynchronous call.
3124 mUploadTransferred = 0;
3125 mUploadTotal = 0;
3126 // By default we don't have any upload, so mark upload complete.
3127 mUploadComplete = true;
3128 mErrorLoad = ErrorType::eOK;
3129 mErrorLoadDetail = NS_OK;
3130 mLoadTotal = -1;
3131 nsCOMPtr<nsIInputStream> uploadStream;
3132 nsAutoCString uploadContentType;
3133 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
3134 if (aBody && httpChannel && !mRequestMethod.EqualsLiteral("GET") &&
3135 !mRequestMethod.EqualsLiteral("HEAD")) {
3136 nsAutoCString charset;
3137 nsAutoCString defaultContentType;
3138 uint64_t size_u64;
3139 aRv = aBody->GetAsStream(getter_AddRefs(uploadStream), &size_u64,
3140 defaultContentType, charset);
3141 if (aRv.Failed()) {
3142 return;
3145 // make sure it fits within js MAX_SAFE_INTEGER
3146 mUploadTotal =
3147 net::InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1;
3149 if (uploadStream) {
3150 // If author set no Content-Type, use the default from GetAsStream().
3151 mAuthorRequestHeaders.Get("content-type", uploadContentType);
3152 if (uploadContentType.IsVoid()) {
3153 uploadContentType = defaultContentType;
3154 } else if (aBodyIsDocumentOrString) {
3155 RefPtr<CMimeType> contentTypeRecord =
3156 CMimeType::Parse(uploadContentType);
3157 nsAutoCString charset;
3158 if (contentTypeRecord &&
3159 contentTypeRecord->GetParameterValue(kLiteralString_charset,
3160 charset) &&
3161 !charset.EqualsIgnoreCase("utf-8")) {
3162 contentTypeRecord->SetParameterValue(kLiteralString_charset,
3163 kLiteralString_UTF_8);
3164 contentTypeRecord->Serialize(uploadContentType);
3166 } else if (!charset.IsEmpty()) {
3167 // We don't want to set a charset for streams.
3168 // Replace all case-insensitive matches of the charset in the
3169 // content-type with the correct case.
3170 RequestHeaders::CharsetIterator iter(uploadContentType);
3171 while (iter.Next()) {
3172 if (!iter.Equals(charset, nsCaseInsensitiveCStringComparator)) {
3173 iter.Replace(charset);
3178 mUploadComplete = false;
3182 ResetResponse();
3184 // Check if we should enable cross-origin upload listeners.
3185 if (mUpload && mUpload->HasListeners()) {
3186 mFlagHadUploadListenersOnSend = true;
3189 mIsMappedArrayBuffer = false;
3190 if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
3191 StaticPrefs::dom_mapped_arraybuffer_enabled()) {
3192 nsCOMPtr<nsIURI> uri;
3193 nsAutoCString scheme;
3195 aRv = mChannel->GetURI(getter_AddRefs(uri));
3196 if (!aRv.Failed()) {
3197 uri->GetScheme(scheme);
3198 if (scheme.LowerCaseEqualsLiteral("jar")) {
3199 mIsMappedArrayBuffer = true;
3204 aRv = InitiateFetch(uploadStream.forget(), mUploadTotal, uploadContentType);
3205 if (aRv.Failed()) {
3206 return;
3209 // Start our timeout
3210 mRequestSentTime = PR_Now();
3211 StartTimeoutTimer();
3213 mWaitingForOnStopRequest = true;
3215 // Step 8
3216 mFlagSend = true;
3218 // If we're synchronous, spin an event loop here and wait
3219 RefPtr<Document> suspendedDoc;
3220 if (mFlagSynchronous) {
3221 auto scopeExit = MakeScopeExit([&] {
3222 CancelSyncTimeoutTimer();
3223 ResumeTimeout();
3224 ResumeEventDispatching();
3226 Maybe<AutoSuppressEventHandling> autoSuppress;
3228 mFlagSyncLooping = true;
3230 if (GetOwner()) {
3231 if (nsCOMPtr<nsPIDOMWindowOuter> topWindow =
3232 GetOwner()->GetOuterWindow()->GetInProcessTop()) {
3233 if (nsCOMPtr<nsPIDOMWindowInner> topInner =
3234 topWindow->GetCurrentInnerWindow()) {
3235 suspendedDoc = topWindow->GetExtantDoc();
3236 autoSuppress.emplace(topWindow->GetBrowsingContext());
3237 topInner->Suspend();
3238 mResumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
3243 SuspendEventDispatching();
3244 StopProgressEventTimer();
3246 SyncTimeoutType syncTimeoutType = MaybeStartSyncTimeoutTimer();
3247 if (syncTimeoutType == eErrorOrExpired) {
3248 Abort();
3249 aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
3250 return;
3253 nsAutoSyncOperation sync(suspendedDoc,
3254 SyncOperationBehavior::eSuspendInput);
3255 if (!SpinEventLoopUntil("XMLHttpRequestMainThread::SendInternal"_ns,
3256 [&]() { return !mFlagSyncLooping; })) {
3257 aRv.Throw(NS_ERROR_UNEXPECTED);
3258 return;
3261 // Time expired... We should throw.
3262 if (syncTimeoutType == eTimerStarted && !mSyncTimeoutTimer) {
3263 aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
3264 return;
3266 } else {
3267 // Now that we've successfully opened the channel, we can change state. Note
3268 // that this needs to come after the AsyncOpen() and rv check, because this
3269 // can run script that would try to restart this request, and that could end
3270 // up doing our AsyncOpen on a null channel if the reentered AsyncOpen
3271 // fails.
3272 StopProgressEventTimer();
3274 // Upload phase beginning; start the progress event timer if necessary.
3275 if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) {
3276 StartProgressEventTimer();
3278 // Dispatch loadstart events
3279 DispatchProgressEvent(this, Events::loadstart, 0, -1);
3280 if (mUpload && !mUploadComplete) {
3281 DispatchProgressEvent(mUpload, Events::loadstart, 0, mUploadTotal);
3285 if (!mChannel) {
3286 aRv = MaybeSilentSendFailure(NS_ERROR_DOM_NETWORK_ERR);
3290 // http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader
3291 void XMLHttpRequestMainThread::SetRequestHeader(const nsACString& aName,
3292 const nsACString& aValue,
3293 ErrorResult& aRv) {
3294 NOT_CALLABLE_IN_SYNC_SEND_RV
3296 // Step 1
3297 if (mState != XMLHttpRequest_Binding::OPENED) {
3298 aRv.ThrowInvalidStateError("XMLHttpRequest state must be OPENED.");
3299 return;
3302 // Step 2
3303 if (mFlagSend) {
3304 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3305 return;
3308 // Step 3
3309 nsAutoCString value;
3310 NS_TrimHTTPWhitespace(aValue, value);
3312 // Step 4
3313 if (!NS_IsValidHTTPToken(aName) || !NS_IsReasonableHTTPHeaderValue(value)) {
3314 aRv.Throw(NS_ERROR_DOM_INVALID_HEADER_NAME);
3315 return;
3318 // Step 5
3319 bool isPrivilegedCaller = IsSystemXHR();
3320 bool isForbiddenHeader =
3321 nsContentUtils::IsForbiddenRequestHeader(aName, aValue);
3322 if (!isPrivilegedCaller && isForbiddenHeader) {
3323 AutoTArray<nsString, 1> params;
3324 CopyUTF8toUTF16(aName, *params.AppendElement());
3325 LogMessage("ForbiddenHeaderWarning", GetOwner(), params);
3326 return;
3329 // Step 6.1
3330 // Skipping for now, as normalizing the case of header names may not be
3331 // web-compatible. See bug 1285036.
3333 // Step 6.2-6.3
3334 // Gecko-specific: invalid headers can be set by privileged
3335 // callers, but will not merge.
3336 if (isPrivilegedCaller && isForbiddenHeader) {
3337 mAuthorRequestHeaders.Set(aName, value);
3338 } else {
3339 mAuthorRequestHeaders.MergeOrSet(aName, value);
3343 void XMLHttpRequestMainThread::SetTimeout(uint32_t aTimeout, ErrorResult& aRv) {
3344 NOT_CALLABLE_IN_SYNC_SEND_RV
3346 if (mFlagSynchronous && mState != XMLHttpRequest_Binding::UNSENT &&
3347 HasOrHasHadOwner()) {
3348 /* Timeout is not supported for synchronous requests with an owning window,
3349 per XHR2 spec. */
3350 LogMessage("TimeoutSyncXHRWarning", GetOwner());
3351 aRv.ThrowInvalidAccessError(
3352 "synchronous XMLHttpRequests do not support timeout and responseType");
3353 return;
3356 mTimeoutMilliseconds = aTimeout;
3357 if (mRequestSentTime) {
3358 StartTimeoutTimer();
3362 nsIEventTarget* XMLHttpRequestMainThread::GetTimerEventTarget() {
3363 if (nsIGlobalObject* global = GetOwnerGlobal()) {
3364 return global->SerialEventTarget();
3366 return nullptr;
3369 nsresult XMLHttpRequestMainThread::DispatchToMainThread(
3370 already_AddRefed<nsIRunnable> aRunnable) {
3371 DEBUG_WORKERREFS;
3372 if (nsIGlobalObject* global = GetOwnerGlobal()) {
3373 return global->Dispatch(std::move(aRunnable));
3375 return NS_DispatchToMainThread(std::move(aRunnable));
3378 void XMLHttpRequestMainThread::StartTimeoutTimer() {
3379 DEBUG_WORKERREFS;
3380 MOZ_ASSERT(
3381 mRequestSentTime,
3382 "StartTimeoutTimer mustn't be called before the request was sent!");
3383 if (mState == XMLHttpRequest_Binding::DONE) {
3384 // do nothing!
3385 return;
3388 CancelTimeoutTimer();
3390 if (!mTimeoutMilliseconds) {
3391 return;
3394 if (!mTimeoutTimer) {
3395 mTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
3397 uint32_t elapsed =
3398 (uint32_t)((PR_Now() - mRequestSentTime) / PR_USEC_PER_MSEC);
3399 mTimeoutTimer->InitWithCallback(
3400 this, mTimeoutMilliseconds > elapsed ? mTimeoutMilliseconds - elapsed : 0,
3401 nsITimer::TYPE_ONE_SHOT);
3404 uint16_t XMLHttpRequestMainThread::ReadyState() const { return mState; }
3406 void XMLHttpRequestMainThread::OverrideMimeType(const nsAString& aMimeType,
3407 ErrorResult& aRv) {
3408 NOT_CALLABLE_IN_SYNC_SEND_RV
3410 if (mState == XMLHttpRequest_Binding::LOADING ||
3411 mState == XMLHttpRequest_Binding::DONE) {
3412 aRv.ThrowInvalidStateError(
3413 "Cannot call 'overrideMimeType()' on XMLHttpRequest after 'send()' "
3414 "(when its state is LOADING or DONE).");
3415 return;
3418 RefPtr<MimeType> parsed = MimeType::Parse(aMimeType);
3419 if (parsed) {
3420 parsed->Serialize(mOverrideMimeType);
3421 } else {
3422 mOverrideMimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
3426 bool XMLHttpRequestMainThread::MozBackgroundRequest() const {
3427 return mFlagBackgroundRequest;
3430 void XMLHttpRequestMainThread::SetMozBackgroundRequestExternal(
3431 bool aMozBackgroundRequest, ErrorResult& aRv) {
3432 if (!IsSystemXHR()) {
3433 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
3434 return;
3437 if (mState != XMLHttpRequest_Binding::UNSENT) {
3438 // Can't change this while we're in the middle of something.
3439 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3440 return;
3443 mFlagBackgroundRequest = aMozBackgroundRequest;
3446 void XMLHttpRequestMainThread::SetMozBackgroundRequest(
3447 bool aMozBackgroundRequest, ErrorResult& aRv) {
3448 // No errors for this webIDL method on main-thread.
3449 SetMozBackgroundRequestExternal(aMozBackgroundRequest, IgnoreErrors());
3452 void XMLHttpRequestMainThread::SetOriginStack(
3453 UniquePtr<SerializedStackHolder> aOriginStack) {
3454 mOriginStack = std::move(aOriginStack);
3457 void XMLHttpRequestMainThread::SetSource(
3458 UniquePtr<ProfileChunkedBuffer> aSource) {
3459 if (!mChannel) {
3460 return;
3462 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
3464 if (httpChannel) {
3465 httpChannel->SetSource(std::move(aSource));
3469 bool XMLHttpRequestMainThread::WithCredentials() const {
3470 return mFlagACwithCredentials;
3473 void XMLHttpRequestMainThread::SetWithCredentials(bool aWithCredentials,
3474 ErrorResult& aRv) {
3475 NOT_CALLABLE_IN_SYNC_SEND_RV
3477 // Return error if we're already processing a request. Note that we can't use
3478 // ReadyState() here, because it can't differentiate between "opened" and
3479 // "sent", so we use mState directly.
3481 if ((mState != XMLHttpRequest_Binding::UNSENT &&
3482 mState != XMLHttpRequest_Binding::OPENED) ||
3483 mFlagSend || mIsAnon) {
3484 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3485 return;
3488 mFlagACwithCredentials = aWithCredentials;
3491 nsresult XMLHttpRequestMainThread::ChangeState(uint16_t aState,
3492 bool aBroadcast) {
3493 mState = aState;
3494 nsresult rv = NS_OK;
3496 if (aState != XMLHttpRequest_Binding::HEADERS_RECEIVED &&
3497 aState != XMLHttpRequest_Binding::LOADING) {
3498 StopProgressEventTimer();
3501 if (aBroadcast &&
3502 (!mFlagSynchronous || aState == XMLHttpRequest_Binding::OPENED ||
3503 aState == XMLHttpRequest_Binding::DONE)) {
3504 rv = FireReadystatechangeEvent();
3507 return rv;
3510 /////////////////////////////////////////////////////
3511 // nsIChannelEventSink methods:
3513 NS_IMETHODIMP
3514 XMLHttpRequestMainThread::AsyncOnChannelRedirect(
3515 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
3516 nsIAsyncVerifyRedirectCallback* callback) {
3517 DEBUG_WORKERREFS;
3518 MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
3520 // Prepare to receive callback
3521 mRedirectCallback = callback;
3522 mNewRedirectChannel = aNewChannel;
3524 if (mChannelEventSink) {
3525 nsCOMPtr<nsIAsyncVerifyRedirectCallback> fwd = EnsureXPCOMifier();
3527 nsresult rv = mChannelEventSink->AsyncOnChannelRedirect(
3528 aOldChannel, aNewChannel, aFlags, fwd);
3529 if (NS_FAILED(rv)) {
3530 mRedirectCallback = nullptr;
3531 mNewRedirectChannel = nullptr;
3533 return rv;
3536 // we need to strip Authentication headers for cross-origin requests
3537 // Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
3538 bool stripAuth =
3539 StaticPrefs::network_fetch_redirect_stripAuthHeader() &&
3540 NS_ShouldRemoveAuthHeaderOnRedirect(aOldChannel, aNewChannel, aFlags);
3542 OnRedirectVerifyCallback(NS_OK, stripAuth);
3544 return NS_OK;
3547 nsresult XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result,
3548 bool aStripAuth) {
3549 DEBUG_WORKERREFS;
3550 NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
3551 NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
3553 if (NS_SUCCEEDED(result)) {
3554 bool rewriteToGET = false;
3555 nsCOMPtr<nsIHttpChannel> oldHttpChannel = GetCurrentHttpChannel();
3556 // Fetch 4.4.11
3557 Unused << oldHttpChannel->ShouldStripRequestBodyHeader(mRequestMethod,
3558 &rewriteToGET);
3560 mChannel = mNewRedirectChannel;
3562 nsCOMPtr<nsIHttpChannel> newHttpChannel(do_QueryInterface(mChannel));
3563 if (newHttpChannel) {
3564 // Ensure all original headers are duplicated for the new channel (bug
3565 // #553888)
3566 mAuthorRequestHeaders.ApplyToChannel(newHttpChannel, rewriteToGET,
3567 aStripAuth);
3569 } else {
3570 mErrorLoad = ErrorType::eRedirect;
3571 mErrorLoadDetail = result;
3574 mNewRedirectChannel = nullptr;
3576 mRedirectCallback->OnRedirectVerifyCallback(result);
3577 mRedirectCallback = nullptr;
3579 // It's important that we return success here. If we return the result code
3580 // that we were passed, JavaScript callers who cancel the redirect will wind
3581 // up throwing an exception in the process.
3582 return NS_OK;
3585 /////////////////////////////////////////////////////
3586 // nsIProgressEventSink methods:
3589 NS_IMETHODIMP
3590 XMLHttpRequestMainThread::OnProgress(nsIRequest* aRequest, int64_t aProgress,
3591 int64_t aProgressMax) {
3592 DEBUG_WORKERREFS;
3593 // When uploading, OnProgress reports also headers in aProgress and
3594 // aProgressMax. So, try to remove the headers, if possible.
3595 bool lengthComputable = (aProgressMax != -1);
3596 if (InUploadPhase()) {
3597 int64_t loaded = aProgress;
3598 if (lengthComputable) {
3599 int64_t headerSize = aProgressMax - mUploadTotal;
3600 loaded -= headerSize;
3602 mUploadTransferred = loaded;
3603 mProgressSinceLastProgressEvent = true;
3605 if (!mFlagSynchronous && !mProgressTimerIsActive) {
3606 StartProgressEventTimer();
3608 } else {
3609 mLoadTotal = aProgressMax;
3610 mLoadTransferred = aProgress;
3611 // OnDataAvailable() handles mProgressSinceLastProgressEvent
3612 // for the download phase.
3615 if (mProgressEventSink) {
3616 mProgressEventSink->OnProgress(aRequest, aProgress, aProgressMax);
3619 return NS_OK;
3622 NS_IMETHODIMP
3623 XMLHttpRequestMainThread::OnStatus(nsIRequest* aRequest, nsresult aStatus,
3624 const char16_t* aStatusArg) {
3625 DEBUG_WORKERREFS;
3626 if (mProgressEventSink) {
3627 mProgressEventSink->OnStatus(aRequest, aStatus, aStatusArg);
3630 return NS_OK;
3633 bool XMLHttpRequestMainThread::AllowUploadProgress() {
3634 return !IsCrossSiteCORSRequest() || mFlagHadUploadListenersOnSend;
3637 /////////////////////////////////////////////////////
3638 // nsIInterfaceRequestor methods:
3640 NS_IMETHODIMP
3641 XMLHttpRequestMainThread::GetInterface(const nsIID& aIID, void** aResult) {
3642 nsresult rv;
3644 // Make sure to return ourselves for the channel event sink interface and
3645 // progress event sink interface, no matter what. We can forward these to
3646 // mNotificationCallbacks if it wants to get notifications for them. But we
3647 // need to see these notifications for proper functioning.
3648 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
3649 mChannelEventSink = do_GetInterface(mNotificationCallbacks);
3650 *aResult = static_cast<nsIChannelEventSink*>(EnsureXPCOMifier().take());
3651 return NS_OK;
3652 } else if (aIID.Equals(NS_GET_IID(nsIProgressEventSink))) {
3653 mProgressEventSink = do_GetInterface(mNotificationCallbacks);
3654 *aResult = static_cast<nsIProgressEventSink*>(EnsureXPCOMifier().take());
3655 return NS_OK;
3658 // Now give mNotificationCallbacks (if non-null) a chance to return the
3659 // desired interface.
3660 if (mNotificationCallbacks) {
3661 rv = mNotificationCallbacks->GetInterface(aIID, aResult);
3662 if (NS_SUCCEEDED(rv)) {
3663 NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!");
3664 return rv;
3668 if (!mFlagBackgroundRequest && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
3669 aIID.Equals(NS_GET_IID(nsIAuthPrompt2)))) {
3670 nsCOMPtr<nsIPromptFactory> wwatch =
3671 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
3672 NS_ENSURE_SUCCESS(rv, rv);
3674 // Get the an auth prompter for our window so that the parenting
3675 // of the dialogs works as it should when using tabs.
3676 nsCOMPtr<nsPIDOMWindowOuter> window;
3677 if (GetOwner()) {
3678 window = GetOwner()->GetOuterWindow();
3680 return wwatch->GetPrompt(window, aIID, reinterpret_cast<void**>(aResult));
3683 // Now check for the various XHR non-DOM interfaces, except
3684 // nsIProgressEventSink and nsIChannelEventSink which we already
3685 // handled above.
3686 if (aIID.Equals(NS_GET_IID(nsIStreamListener))) {
3687 *aResult = static_cast<nsIStreamListener*>(EnsureXPCOMifier().take());
3688 return NS_OK;
3690 if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) {
3691 *aResult = static_cast<nsIRequestObserver*>(EnsureXPCOMifier().take());
3692 return NS_OK;
3694 if (aIID.Equals(NS_GET_IID(nsITimerCallback))) {
3695 *aResult = static_cast<nsITimerCallback*>(EnsureXPCOMifier().take());
3696 return NS_OK;
3699 return QueryInterface(aIID, aResult);
3702 void XMLHttpRequestMainThread::GetInterface(
3703 JSContext* aCx, JS::Handle<JS::Value> aIID,
3704 JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
3705 dom::GetInterface(aCx, this, aIID, aRetval, aRv);
3708 XMLHttpRequestUpload* XMLHttpRequestMainThread::GetUpload(ErrorResult& aRv) {
3709 if (!mUpload) {
3710 mUpload = new XMLHttpRequestUpload(this);
3712 return mUpload;
3715 bool XMLHttpRequestMainThread::MozAnon() const { return mIsAnon; }
3717 bool XMLHttpRequestMainThread::MozSystem() const { return IsSystemXHR(); }
3719 void XMLHttpRequestMainThread::HandleTimeoutCallback() {
3720 DEBUG_WORKERREFS;
3721 if (mState == XMLHttpRequest_Binding::DONE) {
3722 MOZ_ASSERT_UNREACHABLE(
3723 "XMLHttpRequestMainThread::HandleTimeoutCallback "
3724 "with completed request");
3725 // do nothing!
3726 return;
3729 mFlagTimedOut = true;
3730 CloseRequestWithError(Events::timeout);
3733 void XMLHttpRequestMainThread::CancelTimeoutTimer() {
3734 DEBUG_WORKERREFS;
3735 if (mTimeoutTimer) {
3736 mTimeoutTimer->Cancel();
3737 mTimeoutTimer = nullptr;
3741 NS_IMETHODIMP
3742 XMLHttpRequestMainThread::Notify(nsITimer* aTimer) {
3743 DEBUG_WORKERREFS;
3744 if (mProgressNotifier == aTimer) {
3745 HandleProgressTimerCallback();
3746 return NS_OK;
3749 if (mTimeoutTimer == aTimer) {
3750 HandleTimeoutCallback();
3751 return NS_OK;
3754 if (mSyncTimeoutTimer == aTimer) {
3755 HandleSyncTimeoutTimer();
3756 return NS_OK;
3759 // Just in case some JS user wants to QI to nsITimerCallback and play with
3760 // us...
3761 NS_WARNING("Unexpected timer!");
3762 return NS_ERROR_INVALID_POINTER;
3765 void XMLHttpRequestMainThread::HandleProgressTimerCallback() {
3766 DEBUG_WORKERREFS;
3767 // Don't fire the progress event if mLoadTotal is 0, see XHR spec step 6.1
3768 if (!mLoadTotal && mLoadTransferred) {
3769 return;
3772 mProgressTimerIsActive = false;
3774 if (!mProgressSinceLastProgressEvent || mErrorLoad != ErrorType::eOK) {
3775 return;
3778 if (InUploadPhase()) {
3779 if (mUpload && !mUploadComplete && mFlagHadUploadListenersOnSend) {
3780 DispatchProgressEvent(mUpload, Events::progress, mUploadTransferred,
3781 mUploadTotal);
3783 } else {
3784 FireReadystatechangeEvent();
3785 DispatchProgressEvent(this, Events::progress, mLoadTransferred, mLoadTotal);
3788 mProgressSinceLastProgressEvent = false;
3790 StartProgressEventTimer();
3793 void XMLHttpRequestMainThread::StopProgressEventTimer() {
3794 if (mProgressNotifier) {
3795 mProgressTimerIsActive = false;
3796 mProgressNotifier->Cancel();
3800 void XMLHttpRequestMainThread::StartProgressEventTimer() {
3801 if (!mProgressNotifier) {
3802 mProgressNotifier = NS_NewTimer(GetTimerEventTarget());
3804 if (mProgressNotifier) {
3805 mProgressTimerIsActive = true;
3806 mProgressNotifier->Cancel();
3807 mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
3808 nsITimer::TYPE_ONE_SHOT);
3812 XMLHttpRequestMainThread::SyncTimeoutType
3813 XMLHttpRequestMainThread::MaybeStartSyncTimeoutTimer() {
3814 MOZ_ASSERT(mFlagSynchronous);
3816 Document* doc = GetDocumentIfCurrent();
3817 if (!doc || !doc->GetPageUnloadingEventTimeStamp()) {
3818 return eNoTimerNeeded;
3821 // If we are in a beforeunload or a unload event, we must force a timeout.
3822 TimeDuration diff =
3823 (TimeStamp::NowLoRes() - doc->GetPageUnloadingEventTimeStamp());
3824 if (diff.ToMilliseconds() > MAX_SYNC_TIMEOUT_WHEN_UNLOADING) {
3825 return eErrorOrExpired;
3828 mSyncTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
3829 if (!mSyncTimeoutTimer) {
3830 return eErrorOrExpired;
3833 uint32_t timeout = MAX_SYNC_TIMEOUT_WHEN_UNLOADING - diff.ToMilliseconds();
3834 nsresult rv = mSyncTimeoutTimer->InitWithCallback(this, timeout,
3835 nsITimer::TYPE_ONE_SHOT);
3836 return NS_FAILED(rv) ? eErrorOrExpired : eTimerStarted;
3839 void XMLHttpRequestMainThread::HandleSyncTimeoutTimer() {
3840 MOZ_ASSERT(mSyncTimeoutTimer);
3841 MOZ_ASSERT(mFlagSyncLooping);
3843 CancelSyncTimeoutTimer();
3844 Abort();
3845 mErrorLoadDetail = NS_ERROR_DOM_TIMEOUT_ERR;
3848 void XMLHttpRequestMainThread::CancelSyncTimeoutTimer() {
3849 if (mSyncTimeoutTimer) {
3850 mSyncTimeoutTimer->Cancel();
3851 mSyncTimeoutTimer = nullptr;
3855 already_AddRefed<nsXMLHttpRequestXPCOMifier>
3856 XMLHttpRequestMainThread::EnsureXPCOMifier() {
3857 if (!mXPCOMifier) {
3858 mXPCOMifier = new nsXMLHttpRequestXPCOMifier(this);
3860 RefPtr<nsXMLHttpRequestXPCOMifier> newRef(mXPCOMifier);
3861 return newRef.forget();
3864 bool XMLHttpRequestMainThread::ShouldBlockAuthPrompt() {
3865 // Verify that it's ok to prompt for credentials here, per spec
3866 // http://xhr.spec.whatwg.org/#the-send%28%29-method
3868 if (mAuthorRequestHeaders.Has("authorization")) {
3869 return true;
3872 nsCOMPtr<nsIURI> uri;
3873 nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
3874 if (NS_WARN_IF(NS_FAILED(rv))) {
3875 return false;
3878 // Also skip if a username and/or password is provided in the URI.
3879 bool hasUserPass;
3880 return NS_SUCCEEDED(uri->GetHasUserPass(&hasUserPass)) && hasUserPass;
3883 void XMLHttpRequestMainThread::TruncateResponseText() {
3884 mResponseText.Truncate();
3885 XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
3888 NS_IMPL_ISUPPORTS(XMLHttpRequestMainThread::nsHeaderVisitor,
3889 nsIHttpHeaderVisitor)
3891 NS_IMETHODIMP XMLHttpRequestMainThread::nsHeaderVisitor::VisitHeader(
3892 const nsACString& header, const nsACString& value) {
3893 if (mXHR.IsSafeHeader(header, mHttpChannel)) {
3894 nsAutoCString lowerHeader(header);
3895 ToLowerCase(lowerHeader);
3896 if (!mHeaderList.InsertElementSorted(HeaderEntry(lowerHeader, value),
3897 fallible)) {
3898 return NS_ERROR_OUT_OF_MEMORY;
3901 return NS_OK;
3904 XMLHttpRequestMainThread::nsHeaderVisitor::nsHeaderVisitor(
3905 const XMLHttpRequestMainThread& aXMLHttpRequest,
3906 NotNull<nsIHttpChannel*> aHttpChannel)
3907 : mXHR(aXMLHttpRequest), mHttpChannel(aHttpChannel) {}
3909 XMLHttpRequestMainThread::nsHeaderVisitor::~nsHeaderVisitor() = default;
3911 void XMLHttpRequestMainThread::MaybeCreateBlobStorage() {
3912 DEBUG_WORKERREFS;
3913 MOZ_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
3915 if (mBlobStorage) {
3916 return;
3919 MutableBlobStorage::MutableBlobStorageType storageType =
3920 BasePrincipal::Cast(mPrincipal)->PrivateBrowsingId() == 0
3921 ? MutableBlobStorage::eCouldBeInTemporaryFile
3922 : MutableBlobStorage::eOnlyInMemory;
3924 nsCOMPtr<nsIEventTarget> eventTarget;
3925 if (nsIGlobalObject* global = GetOwnerGlobal()) {
3926 eventTarget = global->SerialEventTarget();
3929 mBlobStorage = new MutableBlobStorage(storageType, eventTarget);
3932 void XMLHttpRequestMainThread::BlobStoreCompleted(
3933 MutableBlobStorage* aBlobStorage, BlobImpl* aBlobImpl, nsresult aRv) {
3934 DEBUG_WORKERREFS;
3935 // Ok, the state is changed...
3936 if (mBlobStorage != aBlobStorage || NS_FAILED(aRv)) {
3937 return;
3940 MOZ_ASSERT(mState != XMLHttpRequest_Binding::DONE);
3942 mResponseBlobImpl = aBlobImpl;
3943 mBlobStorage = nullptr;
3945 ChangeStateToDone(mFlagSyncLooping);
3948 NS_IMETHODIMP
3949 XMLHttpRequestMainThread::GetName(nsACString& aName) {
3950 aName.AssignLiteral("XMLHttpRequest");
3951 return NS_OK;
3954 // nsXMLHttpRequestXPCOMifier implementation
3955 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpRequestXPCOMifier)
3956 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
3957 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
3958 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
3959 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
3960 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
3961 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
3962 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
3963 NS_INTERFACE_MAP_ENTRY(nsINamed)
3964 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
3965 NS_INTERFACE_MAP_END
3967 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpRequestXPCOMifier)
3968 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpRequestXPCOMifier)
3970 // Can't NS_IMPL_CYCLE_COLLECTION( because mXHR has ambiguous
3971 // inheritance from nsISupports.
3972 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequestXPCOMifier)
3974 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpRequestXPCOMifier)
3975 if (tmp->mXHR) {
3976 tmp->mXHR->mXPCOMifier = nullptr;
3978 NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR)
3979 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
3981 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpRequestXPCOMifier)
3982 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXHR)
3983 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
3985 NS_IMETHODIMP
3986 nsXMLHttpRequestXPCOMifier::GetInterface(const nsIID& aIID, void** aResult) {
3987 // Return ourselves for the things we implement (except
3988 // nsIInterfaceRequestor) and the XHR for the rest.
3989 if (!aIID.Equals(NS_GET_IID(nsIInterfaceRequestor))) {
3990 nsresult rv = QueryInterface(aIID, aResult);
3991 if (NS_SUCCEEDED(rv)) {
3992 return rv;
3996 return mXHR->GetInterface(aIID, aResult);
3999 ArrayBufferBuilder::ArrayBufferBuilder()
4000 : mMutex("ArrayBufferBuilder"),
4001 mDataPtr(nullptr),
4002 mCapacity(0),
4003 mLength(0),
4004 mMapPtr(nullptr),
4005 mNeutered(false) {}
4007 ArrayBufferBuilder::~ArrayBufferBuilder() {
4008 if (mDataPtr) {
4009 JS_free(nullptr, mDataPtr);
4012 if (mMapPtr) {
4013 JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
4014 mMapPtr = nullptr;
4017 mDataPtr = nullptr;
4018 mCapacity = mLength = 0;
4021 bool ArrayBufferBuilder::SetCapacity(uint32_t aNewCap) {
4022 MutexAutoLock lock(mMutex);
4023 return SetCapacityInternal(aNewCap, lock);
4026 bool ArrayBufferBuilder::SetCapacityInternal(
4027 uint32_t aNewCap, const MutexAutoLock& aProofOfLock) {
4028 MOZ_ASSERT(!mMapPtr);
4029 MOZ_ASSERT(!mNeutered);
4031 // To ensure that realloc won't free mDataPtr, use a size of 1
4032 // instead of 0.
4033 uint8_t* newdata = (uint8_t*)js_realloc(mDataPtr, aNewCap ? aNewCap : 1);
4035 if (!newdata) {
4036 return false;
4039 if (aNewCap > mCapacity) {
4040 memset(newdata + mCapacity, 0, aNewCap - mCapacity);
4043 mDataPtr = newdata;
4044 mCapacity = aNewCap;
4045 if (mLength > aNewCap) {
4046 mLength = aNewCap;
4049 return true;
4052 bool ArrayBufferBuilder::Append(const uint8_t* aNewData, uint32_t aDataLen,
4053 uint32_t aMaxGrowth) {
4054 MutexAutoLock lock(mMutex);
4055 MOZ_ASSERT(!mMapPtr);
4056 MOZ_ASSERT(!mNeutered);
4058 CheckedUint32 neededCapacity = mLength;
4059 neededCapacity += aDataLen;
4060 if (!neededCapacity.isValid()) {
4061 return false;
4063 if (mLength + aDataLen > mCapacity) {
4064 CheckedUint32 newcap = mCapacity;
4065 // Double while under aMaxGrowth or if not specified.
4066 if (!aMaxGrowth || mCapacity < aMaxGrowth) {
4067 newcap *= 2;
4068 } else {
4069 newcap += aMaxGrowth;
4072 if (!newcap.isValid()) {
4073 return false;
4076 // But make sure there's always enough to satisfy our request.
4077 if (newcap.value() < neededCapacity.value()) {
4078 newcap = neededCapacity;
4081 if (!SetCapacityInternal(newcap.value(), lock)) {
4082 return false;
4086 // Assert that the region isn't overlapping so we can memcpy.
4087 MOZ_ASSERT(
4088 !AreOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength, aDataLen));
4090 memcpy(mDataPtr + mLength, aNewData, aDataLen);
4091 mLength += aDataLen;
4093 return true;
4096 uint32_t ArrayBufferBuilder::Length() {
4097 MutexAutoLock lock(mMutex);
4098 MOZ_ASSERT(!mNeutered);
4099 return mLength;
4102 uint32_t ArrayBufferBuilder::Capacity() {
4103 MutexAutoLock lock(mMutex);
4104 MOZ_ASSERT(!mNeutered);
4105 return mCapacity;
4108 JSObject* ArrayBufferBuilder::TakeArrayBuffer(JSContext* aCx) {
4109 MutexAutoLock lock(mMutex);
4110 MOZ_DIAGNOSTIC_ASSERT(!mNeutered);
4112 if (mMapPtr) {
4113 JSObject* obj = JS::NewMappedArrayBufferWithContents(aCx, mLength, mMapPtr);
4114 if (!obj) {
4115 JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
4118 mMapPtr = nullptr;
4119 mNeutered = true;
4121 // The memory-mapped contents will be released when the ArrayBuffer becomes
4122 // detached or is GC'd.
4123 return obj;
4126 // we need to check for mLength == 0, because nothing may have been
4127 // added
4128 if (mCapacity > mLength || mLength == 0) {
4129 if (!SetCapacityInternal(mLength, lock)) {
4130 return nullptr;
4134 // |mDataPtr| will be deallocated in ArrayBufferBuilder's destructor when this
4135 // ArrayBuffer allocation failed.
4136 JSObject* obj = JS::NewArrayBufferWithContents(
4137 aCx, mLength, mDataPtr,
4138 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory);
4139 if (!obj) {
4140 return nullptr;
4143 mDataPtr = nullptr;
4144 mCapacity = mLength = 0;
4146 mNeutered = true;
4147 return obj;
4150 nsresult ArrayBufferBuilder::MapToFileInPackage(const nsCString& aFile,
4151 nsIFile* aJarFile) {
4152 MutexAutoLock lock(mMutex);
4153 MOZ_ASSERT(NS_IsMainThread());
4154 MOZ_ASSERT(!mNeutered);
4156 nsresult rv;
4158 // Open Jar file to get related attributes of target file.
4159 RefPtr<nsZipArchive> zip = nsZipArchive::OpenArchive(aJarFile);
4160 if (!zip) {
4161 return NS_ERROR_FAILURE;
4163 nsZipItem* zipItem = zip->GetItem(aFile.get());
4164 if (!zipItem) {
4165 return NS_ERROR_FILE_NOT_FOUND;
4168 // If file was added to the package as stored(uncompressed), map to the
4169 // offset of file in zip package.
4170 if (!zipItem->Compression()) {
4171 uint32_t offset = zip->GetDataOffset(zipItem);
4172 uint32_t size = zipItem->RealSize();
4173 mozilla::AutoFDClose pr_fd;
4174 rv = aJarFile->OpenNSPRFileDesc(PR_RDONLY, 0, getter_Transfers(pr_fd));
4175 if (NS_FAILED(rv)) {
4176 return rv;
4178 mMapPtr = JS::CreateMappedArrayBufferContents(
4179 PR_FileDesc2NativeHandle(pr_fd.get()), offset, size);
4180 if (mMapPtr) {
4181 mLength = size;
4182 return NS_OK;
4185 return NS_ERROR_FAILURE;
4188 /* static */
4189 bool ArrayBufferBuilder::AreOverlappingRegions(const uint8_t* aStart1,
4190 uint32_t aLength1,
4191 const uint8_t* aStart2,
4192 uint32_t aLength2) {
4193 const uint8_t* end1 = aStart1 + aLength1;
4194 const uint8_t* end2 = aStart2 + aLength2;
4196 const uint8_t* max_start = aStart1 > aStart2 ? aStart1 : aStart2;
4197 const uint8_t* min_end = end1 < end2 ? end1 : end2;
4199 return max_start < min_end;
4202 RequestHeaders::RequestHeader* RequestHeaders::Find(const nsACString& aName) {
4203 for (RequestHeaders::RequestHeader& header : mHeaders) {
4204 if (header.mName.Equals(aName, nsCaseInsensitiveCStringComparator)) {
4205 return &header;
4208 return nullptr;
4211 bool RequestHeaders::IsEmpty() const { return mHeaders.IsEmpty(); }
4213 bool RequestHeaders::Has(const char* aName) {
4214 return Has(nsDependentCString(aName));
4217 bool RequestHeaders::Has(const nsACString& aName) { return !!Find(aName); }
4219 void RequestHeaders::Get(const char* aName, nsACString& aValue) {
4220 Get(nsDependentCString(aName), aValue);
4223 void RequestHeaders::Get(const nsACString& aName, nsACString& aValue) {
4224 RequestHeader* header = Find(aName);
4225 if (header) {
4226 aValue = header->mValue;
4227 } else {
4228 aValue.SetIsVoid(true);
4232 void RequestHeaders::Set(const char* aName, const nsACString& aValue) {
4233 Set(nsDependentCString(aName), aValue);
4236 void RequestHeaders::Set(const nsACString& aName, const nsACString& aValue) {
4237 RequestHeader* header = Find(aName);
4238 if (header) {
4239 header->mValue.Assign(aValue);
4240 } else {
4241 RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
4242 mHeaders.AppendElement(newHeader);
4246 void RequestHeaders::MergeOrSet(const char* aName, const nsACString& aValue) {
4247 MergeOrSet(nsDependentCString(aName), aValue);
4250 void RequestHeaders::MergeOrSet(const nsACString& aName,
4251 const nsACString& aValue) {
4252 RequestHeader* header = Find(aName);
4253 if (header) {
4254 header->mValue.AppendLiteral(", ");
4255 header->mValue.Append(aValue);
4256 } else {
4257 RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
4258 mHeaders.AppendElement(newHeader);
4262 void RequestHeaders::Clear() { mHeaders.Clear(); }
4264 void RequestHeaders::ApplyToChannel(nsIHttpChannel* aChannel,
4265 bool aStripRequestBodyHeader,
4266 bool aStripAuthHeader) const {
4267 for (const RequestHeader& header : mHeaders) {
4268 if (aStripRequestBodyHeader &&
4269 (header.mName.LowerCaseEqualsASCII("content-type") ||
4270 header.mName.LowerCaseEqualsASCII("content-encoding") ||
4271 header.mName.LowerCaseEqualsASCII("content-language") ||
4272 header.mName.LowerCaseEqualsASCII("content-location"))) {
4273 continue;
4276 if (aStripAuthHeader &&
4277 header.mName.LowerCaseEqualsASCII("authorization")) {
4278 continue;
4281 // Update referrerInfo to override referrer header in system privileged.
4282 if (header.mName.LowerCaseEqualsASCII("referer")) {
4283 DebugOnly<nsresult> rv = aChannel->SetNewReferrerInfo(
4284 header.mValue, nsIReferrerInfo::ReferrerPolicyIDL::UNSAFE_URL, true);
4285 MOZ_ASSERT(NS_SUCCEEDED(rv));
4287 if (header.mValue.IsEmpty()) {
4288 DebugOnly<nsresult> rv = aChannel->SetEmptyRequestHeader(header.mName);
4289 MOZ_ASSERT(NS_SUCCEEDED(rv));
4290 } else {
4291 DebugOnly<nsresult> rv =
4292 aChannel->SetRequestHeader(header.mName, header.mValue, false);
4293 MOZ_ASSERT(NS_SUCCEEDED(rv));
4298 void RequestHeaders::GetCORSUnsafeHeaders(nsTArray<nsCString>& aArray) const {
4299 for (const RequestHeader& header : mHeaders) {
4300 if (!nsContentUtils::IsCORSSafelistedRequestHeader(header.mName,
4301 header.mValue)) {
4302 aArray.AppendElement(header.mName);
4307 RequestHeaders::CharsetIterator::CharsetIterator(nsACString& aSource)
4308 : mValid(false),
4309 mCurPos(-1),
4310 mCurLen(-1),
4311 mCutoff(aSource.Length()),
4312 mSource(aSource) {}
4314 bool RequestHeaders::CharsetIterator::Equals(
4315 const nsACString& aOther, const nsCStringComparator& aCmp) const {
4316 if (mValid) {
4317 return Substring(mSource, mCurPos, mCurLen).Equals(aOther, aCmp);
4318 } else {
4319 return false;
4323 void RequestHeaders::CharsetIterator::Replace(const nsACString& aReplacement) {
4324 if (mValid) {
4325 mSource.Replace(mCurPos, mCurLen, aReplacement);
4326 mCurLen = aReplacement.Length();
4330 bool RequestHeaders::CharsetIterator::Next() {
4331 int32_t start, end;
4332 nsAutoCString charset;
4334 // Look for another charset declaration in the string, limiting the
4335 // search to only the characters before the parts we've already searched
4336 // (before mCutoff), so that we don't find the same charset twice.
4337 NS_ExtractCharsetFromContentType(Substring(mSource, 0, mCutoff), charset,
4338 &mValid, &start, &end);
4340 if (!mValid) {
4341 return false;
4344 // Everything after the = sign is the part of the charset we want.
4345 mCurPos = mSource.FindChar('=', start) + 1;
4346 mCurLen = end - mCurPos;
4348 // Special case: the extracted charset is quoted with single quotes.
4349 // For the purpose of preserving what was set we want to handle them
4350 // as delimiters (although they aren't really).
4351 if (charset.Length() >= 2 && charset.First() == '\'' &&
4352 charset.Last() == '\'') {
4353 ++mCurPos;
4354 mCurLen -= 2;
4357 mCutoff = start;
4359 return true;
4362 } // namespace mozilla::dom