no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / xhr / XMLHttpRequestMainThread.cpp
blob7c2a65e036bb08b1bc708bdaaed6badbdf561a5f
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/PreloaderBase.h"
44 #include "mozilla/ScopeExit.h"
45 #include "mozilla/SpinEventLoopUntil.h"
46 #include "mozilla/StaticPrefs_dom.h"
47 #include "mozilla/StaticPrefs_network.h"
48 #include "mozilla/StaticPrefs_privacy.h"
49 #include "mozilla/dom/ProgressEvent.h"
50 #include "nsDataChannel.h"
51 #include "nsIJARChannel.h"
52 #include "nsIJARURI.h"
53 #include "nsReadableUtils.h"
54 #include "nsSandboxFlags.h"
56 #include "nsIURI.h"
57 #include "nsIURIMutator.h"
58 #include "nsILoadGroup.h"
59 #include "nsNetUtil.h"
60 #include "nsStringStream.h"
61 #include "nsIAuthPrompt.h"
62 #include "nsIAuthPrompt2.h"
63 #include "nsIClassOfService.h"
64 #include "nsIHttpChannel.h"
65 #include "nsISupportsPriority.h"
66 #include "nsIInterfaceRequestorUtils.h"
67 #include "nsStreamUtils.h"
68 #include "nsThreadUtils.h"
69 #include "nsIUploadChannel.h"
70 #include "nsIUploadChannel2.h"
71 #include "nsXPCOM.h"
72 #include "nsIDOMEventListener.h"
73 #include "nsVariant.h"
74 #include "nsIScriptError.h"
75 #include "nsICachingChannel.h"
76 #include "nsICookieJarSettings.h"
77 #include "nsContentUtils.h"
78 #include "nsCycleCollectionParticipant.h"
79 #include "nsError.h"
80 #include "nsIPromptFactory.h"
81 #include "nsIWindowWatcher.h"
82 #include "nsIConsoleService.h"
83 #include "nsAsyncRedirectVerifyHelper.h"
84 #include "nsStringBuffer.h"
85 #include "nsIFileChannel.h"
86 #include "mozilla/Telemetry.h"
87 #include "js/ArrayBuffer.h" // JS::{Create,Release}MappedArrayBufferContents,New{,Mapped}ArrayBufferWithContents
88 #include "js/JSON.h" // JS_ParseJSON
89 #include "js/MemoryFunctions.h"
90 #include "js/RootingAPI.h" // JS::{{,Mutable}Handle,Rooted}
91 #include "js/Value.h" // JS::{,Undefined}Value
92 #include "jsapi.h" // JS_ClearPendingException
93 #include "GeckoProfiler.h"
94 #include "mozilla/dom/XMLHttpRequestBinding.h"
95 #include "mozilla/Attributes.h"
96 #include "MultipartBlobImpl.h"
97 #include "nsIPermissionManager.h"
98 #include "nsMimeTypes.h"
99 #include "nsIHttpChannelInternal.h"
100 #include "nsCharSeparatedTokenizer.h"
101 #include "nsStreamListenerWrapper.h"
102 #include "nsITimedChannel.h"
103 #include "nsWrapperCacheInlines.h"
104 #include "nsZipArchive.h"
105 #include "mozilla/Preferences.h"
106 #include "private/pprio.h"
107 #include "XMLHttpRequestUpload.h"
109 // Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being
110 // replaced by FileCreatorHelper#CreateFileW.
111 #ifdef CreateFile
112 # undef CreateFile
113 #endif
115 extern mozilla::LazyLogModule gXMLHttpRequestLog;
117 using namespace mozilla::net;
119 namespace mozilla::dom {
121 using EventType = XMLHttpRequest::EventType;
122 using Events = XMLHttpRequest::Events;
124 // Maximum size that we'll grow an ArrayBuffer instead of doubling,
125 // once doubling reaches this threshold
126 const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH = 32 * 1024 * 1024;
127 // start at 32k to avoid lots of doubling right at the start
128 const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE = 32 * 1024;
129 // the maximum Content-Length that we'll preallocate. 1GB. Must fit
130 // in an int32_t!
131 const int32_t XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE =
132 1 * 1024 * 1024 * 1024LL;
134 namespace {
135 const nsString kLiteralString_readystatechange = u"readystatechange"_ns;
136 const nsString kLiteralString_xmlhttprequest = u"xmlhttprequest"_ns;
137 const nsString kLiteralString_DOMContentLoaded = u"DOMContentLoaded"_ns;
138 const nsCString kLiteralString_charset = "charset"_ns;
139 const nsCString kLiteralString_UTF_8 = "UTF-8"_ns;
140 } // namespace
142 #define NS_PROGRESS_EVENT_INTERVAL 50
143 #define MAX_SYNC_TIMEOUT_WHEN_UNLOADING 10000 /* 10 secs */
145 NS_IMPL_ISUPPORTS(nsXHRParseEndListener, nsIDOMEventListener)
147 class nsResumeTimeoutsEvent : public Runnable {
148 public:
149 explicit nsResumeTimeoutsEvent(nsPIDOMWindowInner* aWindow)
150 : Runnable("dom::nsResumeTimeoutsEvent"), mWindow(aWindow) {}
152 NS_IMETHOD Run() override {
153 mWindow->Resume();
154 return NS_OK;
157 private:
158 nsCOMPtr<nsPIDOMWindowInner> mWindow;
161 // This helper function adds the given load flags to the request's existing
162 // load flags.
163 static void AddLoadFlags(nsIRequest* request, nsLoadFlags newFlags) {
164 nsLoadFlags flags;
165 request->GetLoadFlags(&flags);
166 flags |= newFlags;
167 request->SetLoadFlags(flags);
170 // We are in a sync event loop.
171 #define NOT_CALLABLE_IN_SYNC_SEND_RV \
172 if (mFlagSyncLooping || mEventDispatchingSuspended) { \
173 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT); \
174 return; \
177 /////////////////////////////////////////////
180 /////////////////////////////////////////////
182 #ifdef DEBUG
184 // In debug mode, annotate WorkerRefs with the name of the function being
185 // invoked for increased scrutability. Save the previous value on the stack.
186 namespace {
187 struct DebugWorkerRefs {
188 RefPtr<ThreadSafeWorkerRef> mTSWorkerRef;
189 nsCString mPrev;
191 DebugWorkerRefs(RefPtr<ThreadSafeWorkerRef>& aTSWorkerRef,
192 const std::string& aStatus)
193 : mTSWorkerRef(aTSWorkerRef) {
194 if (!mTSWorkerRef) {
195 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Info,
196 ("No WorkerRef during: %s", aStatus.c_str()));
197 return;
200 MOZ_ASSERT(mTSWorkerRef->Private());
202 nsCString status(aStatus.c_str());
203 mPrev = GET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref());
204 SET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref(), status);
207 ~DebugWorkerRefs() {
208 if (!mTSWorkerRef) {
209 return;
212 MOZ_ASSERT(mTSWorkerRef->Private());
214 SET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref(), mPrev);
217 } // namespace
219 # define STREAM_STRING(stuff) \
220 (((const std::ostringstream&)(std::ostringstream() << stuff)) \
221 .str()) // NOLINT
222 # define DEBUG_WORKERREFS \
223 DebugWorkerRefs MOZ_UNIQUE_VAR(debugWR__)(mTSWorkerRef, __func__)
224 # define DEBUG_WORKERREFS1(x) \
225 DebugWorkerRefs MOZ_UNIQUE_VAR(debugWR__)( \
226 mTSWorkerRef, STREAM_STRING(__func__ << ": " << x)) // NOLINT
228 #else
229 # define DEBUG_WORKERREFS void()
230 # define DEBUG_WORKERREFS1(x) void()
231 #endif // DEBUG
233 bool XMLHttpRequestMainThread::sDontWarnAboutSyncXHR = false;
235 XMLHttpRequestMainThread::XMLHttpRequestMainThread(
236 nsIGlobalObject* aGlobalObject)
237 : XMLHttpRequest(aGlobalObject),
238 mResponseBodyDecodedPos(0),
239 mResponseType(XMLHttpRequestResponseType::_empty),
240 mState(XMLHttpRequest_Binding::UNSENT),
241 mFlagSynchronous(false),
242 mFlagAborted(false),
243 mFlagParseBody(false),
244 mFlagSyncLooping(false),
245 mFlagBackgroundRequest(false),
246 mFlagHadUploadListenersOnSend(false),
247 mFlagACwithCredentials(false),
248 mFlagTimedOut(false),
249 mFlagDeleted(false),
250 mFlagSend(false),
251 mUploadTransferred(0),
252 mUploadTotal(0),
253 mUploadComplete(true),
254 mProgressSinceLastProgressEvent(false),
255 mRequestSentTime(0),
256 mTimeoutMilliseconds(0),
257 mErrorLoad(ErrorType::eOK),
258 mErrorLoadDetail(NS_OK),
259 mErrorParsingXML(false),
260 mWaitingForOnStopRequest(false),
261 mProgressTimerIsActive(false),
262 mIsHtml(false),
263 mWarnAboutSyncHtml(false),
264 mLoadTotal(-1),
265 mLoadTransferred(0),
266 mIsSystem(false),
267 mIsAnon(false),
268 mResultJSON(JS::UndefinedValue()),
269 mArrayBufferBuilder(new ArrayBufferBuilder()),
270 mResultArrayBuffer(nullptr),
271 mIsMappedArrayBuffer(false),
272 mXPCOMifier(nullptr),
273 mEventDispatchingSuspended(false),
274 mEofDecoded(false),
275 mDelayedDoneNotifier(nullptr) {
276 DEBUG_WORKERREFS;
277 mozilla::HoldJSObjects(this);
280 XMLHttpRequestMainThread::~XMLHttpRequestMainThread() {
281 DEBUG_WORKERREFS;
282 MOZ_ASSERT(
283 !mDelayedDoneNotifier,
284 "How can we have mDelayedDoneNotifier, which owns us, in destructor?");
286 mFlagDeleted = true;
288 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
289 mState == XMLHttpRequest_Binding::LOADING) {
290 Abort();
293 if (mParseEndListener) {
294 mParseEndListener->SetIsStale();
295 mParseEndListener = nullptr;
298 MOZ_ASSERT(!mFlagSyncLooping, "we rather crash than hang");
299 mFlagSyncLooping = false;
301 mozilla::DropJSObjects(this);
304 void XMLHttpRequestMainThread::Construct(
305 nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings,
306 bool aForWorker, nsIURI* aBaseURI /* = nullptr */,
307 nsILoadGroup* aLoadGroup /* = nullptr */,
308 PerformanceStorage* aPerformanceStorage /* = nullptr */,
309 nsICSPEventListener* aCSPEventListener /* = nullptr */) {
310 DEBUG_WORKERREFS;
311 MOZ_ASSERT(aPrincipal);
312 mPrincipal = aPrincipal;
313 mBaseURI = aBaseURI;
314 mLoadGroup = aLoadGroup;
315 mCookieJarSettings = aCookieJarSettings;
316 mForWorker = aForWorker;
317 mPerformanceStorage = aPerformanceStorage;
318 mCSPEventListener = aCSPEventListener;
321 void XMLHttpRequestMainThread::InitParameters(bool aAnon, bool aSystem) {
322 DEBUG_WORKERREFS;
323 if (!aAnon && !aSystem) {
324 return;
327 // Check for permissions.
328 // Chrome is always allowed access, so do the permission check only
329 // for non-chrome pages.
330 if (!IsSystemXHR() && aSystem) {
331 nsIGlobalObject* global = GetOwnerGlobal();
332 if (NS_WARN_IF(!global)) {
333 SetParameters(aAnon, false);
334 return;
337 nsIPrincipal* principal = global->PrincipalOrNull();
338 if (NS_WARN_IF(!principal)) {
339 SetParameters(aAnon, false);
340 return;
343 nsCOMPtr<nsIPermissionManager> permMgr =
344 components::PermissionManager::Service();
345 if (NS_WARN_IF(!permMgr)) {
346 SetParameters(aAnon, false);
347 return;
350 uint32_t permission;
351 nsresult rv = permMgr->TestPermissionFromPrincipal(
352 principal, "systemXHR"_ns, &permission);
353 if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) {
354 SetParameters(aAnon, false);
355 return;
359 SetParameters(aAnon, aSystem);
362 void XMLHttpRequestMainThread::SetClientInfoAndController(
363 const ClientInfo& aClientInfo,
364 const Maybe<ServiceWorkerDescriptor>& aController) {
365 mClientInfo.emplace(aClientInfo);
366 mController = aController;
369 void XMLHttpRequestMainThread::ResetResponse() {
370 mResponseXML = nullptr;
371 mResponseBody.Truncate();
372 TruncateResponseText();
373 mResponseBlobImpl = nullptr;
374 mResponseBlob = nullptr;
375 mBlobStorage = nullptr;
376 mResultArrayBuffer = nullptr;
377 mArrayBufferBuilder = new ArrayBufferBuilder();
378 mResultJSON.setUndefined();
379 mLoadTransferred = 0;
380 mResponseBodyDecodedPos = 0;
381 mEofDecoded = false;
384 NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestMainThread)
386 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestMainThread,
387 XMLHttpRequestEventTarget)
388 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
389 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
390 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML)
392 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener)
394 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseBlob)
395 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationCallbacks)
397 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink)
398 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink)
400 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
401 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
403 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestMainThread,
404 XMLHttpRequestEventTarget)
405 tmp->mResultArrayBuffer = nullptr;
406 tmp->mArrayBufferBuilder = nullptr;
407 tmp->mResultJSON.setUndefined();
408 tmp->mResponseBlobImpl = nullptr;
410 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
411 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
412 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML)
414 NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener)
416 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseBlob)
417 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationCallbacks)
419 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink)
420 NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink)
422 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
423 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
425 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestMainThread,
426 XMLHttpRequestEventTarget)
427 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
428 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultJSON)
429 NS_IMPL_CYCLE_COLLECTION_TRACE_END
431 bool XMLHttpRequestMainThread::IsCertainlyAliveForCC() const {
432 return mWaitingForOnStopRequest;
435 // QueryInterface implementation for XMLHttpRequestMainThread
436 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XMLHttpRequestMainThread)
437 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
438 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
439 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
440 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
441 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
442 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
443 NS_INTERFACE_MAP_ENTRY(nsINamed)
444 NS_INTERFACE_MAP_ENTRY(nsISizeOfEventTarget)
445 NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
447 NS_IMPL_ADDREF_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
448 NS_IMPL_RELEASE_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
450 void XMLHttpRequestMainThread::DisconnectFromOwner() {
451 XMLHttpRequestEventTarget::DisconnectFromOwner();
452 Abort();
455 size_t XMLHttpRequestMainThread::SizeOfEventTargetIncludingThis(
456 MallocSizeOf aMallocSizeOf) const {
457 size_t n = aMallocSizeOf(this);
458 n += mResponseBody.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
460 // Why is this safe? Because no-one else will report this string. The
461 // other possible sharers of this string are as follows.
463 // - The JS engine could hold copies if the JS code holds references, e.g.
464 // |var text = XHR.responseText|. However, those references will be via JS
465 // external strings, for which the JS memory reporter does *not* report the
466 // chars.
468 // - Binary extensions, but they're *extremely* unlikely to do any memory
469 // reporting.
471 n += mResponseText.SizeOfThis(aMallocSizeOf);
473 return n;
475 // Measurement of the following members may be added later if DMD finds it is
476 // worthwhile:
477 // - lots
480 static void LogMessage(
481 const char* aWarning, nsPIDOMWindowInner* aWindow,
482 const nsTArray<nsString>& aParams = nsTArray<nsString>()) {
483 nsCOMPtr<Document> doc;
484 if (aWindow) {
485 doc = aWindow->GetExtantDoc();
487 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, doc,
488 nsContentUtils::eDOM_PROPERTIES, aWarning,
489 aParams);
492 Document* XMLHttpRequestMainThread::GetResponseXML(ErrorResult& aRv) {
493 if (mResponseType != XMLHttpRequestResponseType::_empty &&
494 mResponseType != XMLHttpRequestResponseType::Document) {
495 aRv.ThrowInvalidStateError(
496 "responseXML is only available if responseType is '' or 'document'.");
497 return nullptr;
499 if (mWarnAboutSyncHtml) {
500 mWarnAboutSyncHtml = false;
501 LogMessage("HTMLSyncXHRWarning", GetOwner());
503 if (mState != XMLHttpRequest_Binding::DONE) {
504 return nullptr;
506 return mResponseXML;
510 * This piece copied from XMLDocument, we try to get the charset
511 * from HTTP headers.
513 nsresult XMLHttpRequestMainThread::DetectCharset() {
514 DEBUG_WORKERREFS;
515 mDecoder = nullptr;
517 if (mResponseType != XMLHttpRequestResponseType::_empty &&
518 mResponseType != XMLHttpRequestResponseType::Text &&
519 mResponseType != XMLHttpRequestResponseType::Json) {
520 return NS_OK;
523 nsAutoCString charsetVal;
524 const Encoding* encoding;
525 bool ok = mChannel && NS_SUCCEEDED(mChannel->GetContentCharset(charsetVal)) &&
526 (encoding = Encoding::ForLabel(charsetVal));
527 if (!ok) {
528 // MS documentation states UTF-8 is default for responseText
529 encoding = UTF_8_ENCODING;
532 if (mResponseType == XMLHttpRequestResponseType::Json &&
533 encoding != UTF_8_ENCODING) {
534 // The XHR spec says only UTF-8 is supported for responseType == "json"
535 LogMessage("JSONCharsetWarning", GetOwner());
536 encoding = UTF_8_ENCODING;
539 // Only sniff the BOM for non-JSON responseTypes
540 if (mResponseType == XMLHttpRequestResponseType::Json) {
541 mDecoder = encoding->NewDecoderWithBOMRemoval();
542 } else {
543 mDecoder = encoding->NewDecoder();
546 return NS_OK;
549 nsresult XMLHttpRequestMainThread::AppendToResponseText(
550 Span<const uint8_t> aBuffer, bool aLast) {
551 // Call this with an empty buffer to send the decoder the signal
552 // that we have hit the end of the stream.
554 NS_ENSURE_STATE(mDecoder);
556 CheckedInt<size_t> destBufferLen =
557 mDecoder->MaxUTF16BufferLength(aBuffer.Length());
559 { // scope for holding the mutex that protects mResponseText
560 XMLHttpRequestStringWriterHelper helper(mResponseText);
562 uint32_t len = helper.Length();
564 destBufferLen += len;
565 if (!destBufferLen.isValid() || destBufferLen.value() > UINT32_MAX) {
566 return NS_ERROR_OUT_OF_MEMORY;
569 auto handleOrErr = helper.BulkWrite(destBufferLen.value());
570 if (handleOrErr.isErr()) {
571 return handleOrErr.unwrapErr();
574 auto handle = handleOrErr.unwrap();
576 uint32_t result;
577 size_t read;
578 size_t written;
579 std::tie(result, read, written, std::ignore) =
580 mDecoder->DecodeToUTF16(aBuffer, handle.AsSpan().From(len), aLast);
581 MOZ_ASSERT(result == kInputEmpty);
582 MOZ_ASSERT(read == aBuffer.Length());
583 len += written;
584 MOZ_ASSERT(len <= destBufferLen.value());
585 handle.Finish(len, false);
586 } // release mutex
588 if (aLast) {
589 // Drop the finished decoder to avoid calling into a decoder
590 // that has finished.
591 mDecoder = nullptr;
592 mEofDecoded = true;
594 return NS_OK;
597 void XMLHttpRequestMainThread::GetResponseText(DOMString& aResponseText,
598 ErrorResult& aRv) {
599 MOZ_DIAGNOSTIC_ASSERT(!mForWorker);
601 XMLHttpRequestStringSnapshot snapshot;
602 GetResponseText(snapshot, aRv);
603 if (aRv.Failed()) {
604 return;
607 if (!snapshot.GetAsString(aResponseText)) {
608 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
609 return;
613 void XMLHttpRequestMainThread::GetResponseText(
614 XMLHttpRequestStringSnapshot& aSnapshot, ErrorResult& aRv) {
615 aSnapshot.Reset();
617 if (mResponseType != XMLHttpRequestResponseType::_empty &&
618 mResponseType != XMLHttpRequestResponseType::Text) {
619 aRv.ThrowInvalidStateError(
620 "responseText is only available if responseType is '' or 'text'.");
621 return;
624 if (mState != XMLHttpRequest_Binding::LOADING &&
625 mState != XMLHttpRequest_Binding::DONE) {
626 return;
629 // Main Fetch step 18 requires to ignore body for head/connect methods.
630 if (mRequestMethod.EqualsLiteral("HEAD") ||
631 mRequestMethod.EqualsLiteral("CONNECT")) {
632 return;
635 // We only decode text lazily if we're also parsing to a doc.
636 // Also, if we've decoded all current data already, then no need to decode
637 // more.
638 if ((!mResponseXML && !mErrorParsingXML) ||
639 (mResponseBodyDecodedPos == mResponseBody.Length() &&
640 (mState != XMLHttpRequest_Binding::DONE || mEofDecoded))) {
641 mResponseText.CreateSnapshot(aSnapshot);
642 return;
645 MatchCharsetAndDecoderToResponseDocument();
647 MOZ_ASSERT(mResponseBodyDecodedPos < mResponseBody.Length() ||
648 mState == XMLHttpRequest_Binding::DONE,
649 "Unexpected mResponseBodyDecodedPos");
650 Span<const uint8_t> span = mResponseBody;
651 aRv = AppendToResponseText(span.From(mResponseBodyDecodedPos),
652 mState == XMLHttpRequest_Binding::DONE);
653 if (aRv.Failed()) {
654 return;
657 mResponseBodyDecodedPos = mResponseBody.Length();
659 if (mEofDecoded) {
660 // Free memory buffer which we no longer need
661 mResponseBody.Truncate();
662 mResponseBodyDecodedPos = 0;
665 mResponseText.CreateSnapshot(aSnapshot);
668 nsresult XMLHttpRequestMainThread::CreateResponseParsedJSON(JSContext* aCx) {
669 if (!aCx) {
670 return NS_ERROR_FAILURE;
673 nsAutoString string;
674 nsresult rv = GetResponseTextForJSON(string);
675 if (NS_WARN_IF(NS_FAILED(rv))) {
676 return rv;
679 // The Unicode converter has already zapped the BOM if there was one
680 JS::Rooted<JS::Value> value(aCx);
681 if (!JS_ParseJSON(aCx, string.BeginReading(), string.Length(), &value)) {
682 return NS_ERROR_FAILURE;
685 mResultJSON = value;
686 return NS_OK;
689 void XMLHttpRequestMainThread::SetResponseType(
690 XMLHttpRequestResponseType aResponseType, ErrorResult& aRv) {
691 NOT_CALLABLE_IN_SYNC_SEND_RV
693 if (mState == XMLHttpRequest_Binding::LOADING ||
694 mState == XMLHttpRequest_Binding::DONE) {
695 aRv.ThrowInvalidStateError(
696 "Cannot set 'responseType' property on XMLHttpRequest after 'send()' "
697 "(when its state is LOADING or DONE).");
698 return;
701 // sync request is not allowed setting responseType in window context
702 if (HasOrHasHadOwner() && mState != XMLHttpRequest_Binding::UNSENT &&
703 mFlagSynchronous) {
704 LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
705 aRv.ThrowInvalidAccessError(
706 "synchronous XMLHttpRequests do not support timeout and responseType");
707 return;
710 // Set the responseType attribute's value to the given value.
711 SetResponseTypeRaw(aResponseType);
714 void XMLHttpRequestMainThread::GetResponse(
715 JSContext* aCx, JS::MutableHandle<JS::Value> aResponse, ErrorResult& aRv) {
716 MOZ_DIAGNOSTIC_ASSERT(!mForWorker);
718 switch (mResponseType) {
719 case XMLHttpRequestResponseType::_empty:
720 case XMLHttpRequestResponseType::Text: {
721 DOMString str;
722 GetResponseText(str, aRv);
723 if (aRv.Failed()) {
724 return;
726 if (!xpc::StringToJsval(aCx, str, aResponse)) {
727 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
729 return;
732 case XMLHttpRequestResponseType::Arraybuffer: {
733 if (mState != XMLHttpRequest_Binding::DONE) {
734 aResponse.setNull();
735 return;
738 if (!mResultArrayBuffer) {
739 mResultArrayBuffer = mArrayBufferBuilder->TakeArrayBuffer(aCx);
740 if (!mResultArrayBuffer) {
741 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
742 return;
745 aResponse.setObject(*mResultArrayBuffer);
746 return;
748 case XMLHttpRequestResponseType::Blob: {
749 if (mState != XMLHttpRequest_Binding::DONE) {
750 aResponse.setNull();
751 return;
754 if (!mResponseBlobImpl) {
755 aResponse.setNull();
756 return;
759 if (!mResponseBlob) {
760 mResponseBlob = Blob::Create(GetOwnerGlobal(), mResponseBlobImpl);
763 if (!GetOrCreateDOMReflector(aCx, mResponseBlob, aResponse)) {
764 aResponse.setNull();
767 return;
769 case XMLHttpRequestResponseType::Document: {
770 if (!mResponseXML || mState != XMLHttpRequest_Binding::DONE) {
771 aResponse.setNull();
772 return;
775 aRv =
776 nsContentUtils::WrapNative(aCx, ToSupports(mResponseXML), aResponse);
777 return;
779 case XMLHttpRequestResponseType::Json: {
780 if (mState != XMLHttpRequest_Binding::DONE) {
781 aResponse.setNull();
782 return;
785 if (mResultJSON.isUndefined()) {
786 aRv = CreateResponseParsedJSON(aCx);
787 TruncateResponseText();
788 if (aRv.Failed()) {
789 // Per spec, errors aren't propagated. null is returned instead.
790 aRv = NS_OK;
791 // It would be nice to log the error to the console. That's hard to
792 // do without calling window.onerror as a side effect, though.
793 JS_ClearPendingException(aCx);
794 mResultJSON.setNull();
797 aResponse.set(mResultJSON);
798 return;
800 default:
801 NS_ERROR("Should not happen");
804 aResponse.setNull();
807 already_AddRefed<BlobImpl> XMLHttpRequestMainThread::GetResponseBlobImpl() {
808 MOZ_DIAGNOSTIC_ASSERT(mForWorker);
809 MOZ_DIAGNOSTIC_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
811 if (mState != XMLHttpRequest_Binding::DONE) {
812 return nullptr;
815 RefPtr<BlobImpl> blobImpl = mResponseBlobImpl;
816 return blobImpl.forget();
819 already_AddRefed<ArrayBufferBuilder>
820 XMLHttpRequestMainThread::GetResponseArrayBufferBuilder() {
821 MOZ_DIAGNOSTIC_ASSERT(mForWorker);
822 MOZ_DIAGNOSTIC_ASSERT(mResponseType ==
823 XMLHttpRequestResponseType::Arraybuffer);
825 if (mState != XMLHttpRequest_Binding::DONE) {
826 return nullptr;
829 RefPtr<ArrayBufferBuilder> builder = mArrayBufferBuilder;
830 return builder.forget();
833 nsresult XMLHttpRequestMainThread::GetResponseTextForJSON(nsAString& aString) {
834 if (mState != XMLHttpRequest_Binding::DONE) {
835 aString.SetIsVoid(true);
836 return NS_OK;
839 if (!mResponseText.GetAsString(aString)) {
840 return NS_ERROR_OUT_OF_MEMORY;
843 return NS_OK;
846 bool XMLHttpRequestMainThread::IsCrossSiteCORSRequest() const {
847 if (!mChannel) {
848 return false;
851 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
852 return loadInfo->GetTainting() == LoadTainting::CORS;
855 bool XMLHttpRequestMainThread::IsDeniedCrossSiteCORSRequest() {
856 if (IsCrossSiteCORSRequest()) {
857 nsresult rv;
858 mChannel->GetStatus(&rv);
859 if (NS_FAILED(rv)) {
860 return true;
863 return false;
866 Maybe<nsBaseChannel::ContentRange>
867 XMLHttpRequestMainThread::GetRequestedContentRange() const {
868 MOZ_ASSERT(mChannel);
869 nsBaseChannel* baseChan = static_cast<nsBaseChannel*>(mChannel.get());
870 if (!baseChan) {
871 return mozilla::Nothing();
873 return baseChan->GetContentRange();
876 void XMLHttpRequestMainThread::GetContentRangeHeader(nsACString& out) const {
877 if (!IsBlobURI(mRequestURL)) {
878 out.SetIsVoid(true);
879 return;
881 Maybe<nsBaseChannel::ContentRange> range = GetRequestedContentRange();
882 if (range.isSome()) {
883 range->AsHeader(out);
884 } else {
885 out.SetIsVoid(true);
889 void XMLHttpRequestMainThread::GetResponseURL(nsAString& aUrl) {
890 aUrl.Truncate();
892 if ((mState == XMLHttpRequest_Binding::UNSENT ||
893 mState == XMLHttpRequest_Binding::OPENED) ||
894 !mChannel) {
895 return;
898 // Make sure we don't leak responseURL information from denied cross-site
899 // requests.
900 if (IsDeniedCrossSiteCORSRequest()) {
901 return;
904 nsCOMPtr<nsIURI> responseUrl;
905 if (NS_FAILED(NS_GetFinalChannelURI(mChannel, getter_AddRefs(responseUrl)))) {
906 return;
909 nsAutoCString temp;
910 responseUrl->GetSpecIgnoringRef(temp);
911 CopyUTF8toUTF16(temp, aUrl);
914 uint32_t XMLHttpRequestMainThread::GetStatus(ErrorResult& aRv) {
915 // Make sure we don't leak status information from denied cross-site
916 // requests.
917 if (IsDeniedCrossSiteCORSRequest()) {
918 return 0;
921 if (mState == XMLHttpRequest_Binding::UNSENT ||
922 mState == XMLHttpRequest_Binding::OPENED) {
923 return 0;
926 if (mErrorLoad != ErrorType::eOK) {
927 // Let's simulate the http protocol for jar/app requests:
928 nsCOMPtr<nsIJARChannel> jarChannel = GetCurrentJARChannel();
929 if (jarChannel) {
930 nsresult status;
931 mChannel->GetStatus(&status);
933 if (status == NS_ERROR_FILE_NOT_FOUND) {
934 return 404; // Not Found
935 } else {
936 return 500; // Internal Error
940 return 0;
943 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
944 if (!httpChannel) {
945 // Pretend like we got a 200/206 response, since our load was successful
946 return IsBlobURI(mRequestURL) && GetRequestedContentRange().isSome() ? 206
947 : 200;
950 uint32_t status;
951 nsresult rv = httpChannel->GetResponseStatus(&status);
952 if (NS_FAILED(rv)) {
953 status = 0;
956 return status;
959 void XMLHttpRequestMainThread::GetStatusText(nsACString& aStatusText,
960 ErrorResult& aRv) {
961 // Return an empty status text on all error loads.
962 aStatusText.Truncate();
964 // Make sure we don't leak status information from denied cross-site
965 // requests.
966 if (IsDeniedCrossSiteCORSRequest()) {
967 return;
970 // Check the current XHR state to see if it is valid to obtain the statusText
971 // value. This check is to prevent the status text for redirects from being
972 // available before all the redirects have been followed and HTTP headers have
973 // been received.
974 if (mState == XMLHttpRequest_Binding::UNSENT ||
975 mState == XMLHttpRequest_Binding::OPENED) {
976 return;
979 if (mErrorLoad != ErrorType::eOK) {
980 return;
983 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
984 if (httpChannel) {
985 Unused << httpChannel->GetResponseStatusText(aStatusText);
986 } else {
987 aStatusText.AssignLiteral("OK");
991 void XMLHttpRequestMainThread::TerminateOngoingFetch(nsresult detail) {
992 DEBUG_WORKERREFS;
993 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
994 mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
995 mState == XMLHttpRequest_Binding::LOADING) {
996 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Info,
997 ("%p TerminateOngoingFetch(0x%" PRIx32 ")", this,
998 static_cast<uint32_t>(detail)));
999 CloseRequest(detail);
1003 void XMLHttpRequestMainThread::CloseRequest(nsresult detail) {
1004 DEBUG_WORKERREFS;
1005 mWaitingForOnStopRequest = false;
1006 mErrorLoad = ErrorType::eTerminated;
1007 mErrorLoadDetail = detail;
1008 if (mChannel) {
1009 mChannel->CancelWithReason(NS_BINDING_ABORTED,
1010 "XMLHttpRequestMainThread::CloseRequest"_ns);
1012 CancelTimeoutTimer();
1015 void XMLHttpRequestMainThread::CloseRequestWithError(
1016 const ErrorProgressEventType& aType) {
1017 DEBUG_WORKERREFS;
1018 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
1019 ("%p CloseRequestWithError(%s)", this, aType.cStr));
1021 CloseRequest(aType.errorCode);
1023 ResetResponse();
1025 // If we're in the destructor, don't risk dispatching an event.
1026 if (mFlagDeleted) {
1027 mFlagSyncLooping = false;
1028 return;
1031 if (mState != XMLHttpRequest_Binding::UNSENT &&
1032 !(mState == XMLHttpRequest_Binding::OPENED && !mFlagSend) &&
1033 mState != XMLHttpRequest_Binding::DONE) {
1034 ChangeState(XMLHttpRequest_Binding::DONE, true);
1036 if (!mFlagSyncLooping) {
1037 if (mUpload && !mUploadComplete) {
1038 mUploadComplete = true;
1039 DispatchProgressEvent(mUpload, aType, 0, -1);
1041 DispatchProgressEvent(this, aType, 0, -1);
1045 // The ChangeState call above calls onreadystatechange handlers which
1046 // if they load a new url will cause XMLHttpRequestMainThread::Open to clear
1047 // the abort state bit. If this occurs we're not uninitialized (bug 361773).
1048 if (mFlagAborted) {
1049 ChangeState(XMLHttpRequest_Binding::UNSENT, false); // IE seems to do it
1052 mFlagSyncLooping = false;
1055 void XMLHttpRequestMainThread::RequestErrorSteps(
1056 const ProgressEventType aEventType, const nsresult aOptionalException,
1057 ErrorResult& aRv) {
1058 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
1059 ("%p RequestErrorSteps(%s,0x%" PRIx32 ")", this, aEventType.cStr,
1060 static_cast<uint32_t>(aOptionalException)));
1062 // Cancel our timers first before setting our state to done, so we don't
1063 // trip any assertions if one fires and asserts that state != done.
1064 CancelTimeoutTimer();
1065 CancelSyncTimeoutTimer();
1066 StopProgressEventTimer();
1068 // Step 1
1069 mState = XMLHttpRequest_Binding::DONE;
1071 // Step 2
1072 mFlagSend = false;
1074 // Step 3
1075 ResetResponse();
1077 // If we're in the destructor, don't risk dispatching an event.
1078 if (mFlagDeleted) {
1079 mFlagSyncLooping = false;
1080 return;
1083 // Step 4
1084 if (mFlagSynchronous && NS_FAILED(aOptionalException)) {
1085 aRv.Throw(aOptionalException);
1086 return;
1089 // Step 5
1090 FireReadystatechangeEvent();
1092 // Step 6
1093 if (mUpload && !mUploadComplete) {
1094 // Step 6-1
1095 mUploadComplete = true;
1097 // Step 6-2
1098 if (mFlagHadUploadListenersOnSend) {
1099 // Steps 6-3, 6-4 (loadend is fired for us)
1100 DispatchProgressEvent(mUpload, aEventType, 0, -1);
1104 // Steps 7 and 8 (loadend is fired for us)
1105 DispatchProgressEvent(this, aEventType, 0, -1);
1108 void XMLHttpRequestMainThread::Abort(ErrorResult& aRv) {
1109 NOT_CALLABLE_IN_SYNC_SEND_RV
1110 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("%p Abort()", this));
1111 AbortInternal(aRv);
1114 void XMLHttpRequestMainThread::AbortInternal(ErrorResult& aRv) {
1115 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("%p AbortInternal()", this));
1116 mFlagAborted = true;
1117 DisconnectDoneNotifier();
1119 // Step 1
1120 TerminateOngoingFetch(NS_ERROR_DOM_ABORT_ERR);
1122 // Step 2
1123 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
1124 mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
1125 mState == XMLHttpRequest_Binding::LOADING) {
1126 RequestErrorSteps(Events::abort, NS_ERROR_DOM_ABORT_ERR, aRv);
1129 // Step 3
1130 if (mState == XMLHttpRequest_Binding::DONE) {
1131 ChangeState(XMLHttpRequest_Binding::UNSENT,
1132 false); // no ReadystateChange event
1135 mFlagSyncLooping = false;
1138 /*Method that checks if it is safe to expose a header value to the client.
1139 It is used to check what headers are exposed for CORS requests.*/
1140 bool XMLHttpRequestMainThread::IsSafeHeader(
1141 const nsACString& aHeader, NotNull<nsIHttpChannel*> aHttpChannel) const {
1142 // See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
1143 if (!IsSystemXHR() && nsContentUtils::IsForbiddenResponseHeader(aHeader)) {
1144 NS_WARNING("blocked access to response header");
1145 return false;
1147 // if this is not a CORS call all headers are safe
1148 if (!IsCrossSiteCORSRequest()) {
1149 return true;
1151 // Check for dangerous headers
1152 // Make sure we don't leak header information from denied cross-site
1153 // requests.
1154 if (mChannel) {
1155 nsresult status;
1156 mChannel->GetStatus(&status);
1157 if (NS_FAILED(status)) {
1158 return false;
1161 const char* kCrossOriginSafeHeaders[] = {
1162 "cache-control", "content-language", "content-type", "content-length",
1163 "expires", "last-modified", "pragma"};
1164 for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
1165 if (aHeader.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
1166 return true;
1169 nsAutoCString headerVal;
1170 // The "Access-Control-Expose-Headers" header contains a comma separated
1171 // list of method names.
1172 Unused << aHttpChannel->GetResponseHeader("Access-Control-Expose-Headers"_ns,
1173 headerVal);
1174 bool isSafe = false;
1175 for (const nsACString& token :
1176 nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
1177 if (token.IsEmpty()) {
1178 continue;
1180 if (!NS_IsValidHTTPToken(token)) {
1181 return false;
1184 if (token.EqualsLiteral("*") && !mFlagACwithCredentials) {
1185 isSafe = true;
1186 } else if (aHeader.Equals(token, nsCaseInsensitiveCStringComparator)) {
1187 isSafe = true;
1191 return isSafe;
1194 bool XMLHttpRequestMainThread::GetContentType(nsACString& aValue) const {
1195 MOZ_ASSERT(mChannel);
1196 nsCOMPtr<nsIURI> uri;
1197 if (NS_SUCCEEDED(mChannel->GetURI(getter_AddRefs(uri))) &&
1198 uri->SchemeIs("data")) {
1199 nsDataChannel* dchan = static_cast<nsDataChannel*>(mChannel.get());
1200 MOZ_ASSERT(dchan);
1201 aValue.Assign(dchan->MimeType());
1202 return true;
1204 if (NS_SUCCEEDED(mChannel->GetContentType(aValue))) {
1205 nsCString value;
1206 if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) {
1207 aValue.AppendLiteral(";charset=");
1208 aValue.Append(value);
1210 return true;
1212 return false;
1214 void XMLHttpRequestMainThread::GetAllResponseHeaders(
1215 nsACString& aResponseHeaders, ErrorResult& aRv) {
1216 NOT_CALLABLE_IN_SYNC_SEND_RV
1218 aResponseHeaders.Truncate();
1220 // If the state is UNSENT or OPENED,
1221 // return the empty string and terminate these steps.
1222 if (mState == XMLHttpRequest_Binding::UNSENT ||
1223 mState == XMLHttpRequest_Binding::OPENED) {
1224 return;
1227 if (mErrorLoad != ErrorType::eOK) {
1228 return;
1231 if (nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel()) {
1232 RefPtr<nsHeaderVisitor> visitor =
1233 new nsHeaderVisitor(*this, WrapNotNull(httpChannel));
1234 if (NS_SUCCEEDED(httpChannel->VisitResponseHeaders(visitor))) {
1235 aResponseHeaders = visitor->Headers();
1237 return;
1240 if (!mChannel) {
1241 return;
1244 // Even non-http channels supply content type.
1245 nsAutoCString value;
1246 if (GetContentType(value)) {
1247 aResponseHeaders.AppendLiteral("Content-Type: ");
1248 aResponseHeaders.Append(value);
1249 aResponseHeaders.AppendLiteral("\r\n");
1252 // Don't provide Content-Length for data URIs
1253 nsCOMPtr<nsIURI> uri;
1254 if (NS_FAILED(mChannel->GetURI(getter_AddRefs(uri))) ||
1255 !uri->SchemeIs("data")) {
1256 int64_t length;
1257 if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1258 aResponseHeaders.AppendLiteral("Content-Length: ");
1259 aResponseHeaders.AppendInt(length);
1260 aResponseHeaders.AppendLiteral("\r\n");
1264 // Should set a Content-Range header for blob scheme.
1265 // From https://fetch.spec.whatwg.org/#scheme-fetch 3.blob.9.20:
1266 // "Set response’s header list to «(`Content-Length`, serializedSlicedLength),
1267 // (`Content-Type`, type), (`Content-Range`, contentRange)»."
1268 GetContentRangeHeader(value);
1269 if (!value.IsVoid()) {
1270 aResponseHeaders.AppendLiteral("Content-Range: ");
1271 aResponseHeaders.Append(value);
1272 aResponseHeaders.AppendLiteral("\r\n");
1276 void XMLHttpRequestMainThread::GetResponseHeader(const nsACString& header,
1277 nsACString& _retval,
1278 ErrorResult& aRv) {
1279 NOT_CALLABLE_IN_SYNC_SEND_RV
1281 _retval.SetIsVoid(true);
1283 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
1285 if (!httpChannel) {
1286 // If the state is UNSENT or OPENED,
1287 // return null and terminate these steps.
1288 if (mState == XMLHttpRequest_Binding::UNSENT ||
1289 mState == XMLHttpRequest_Binding::OPENED) {
1290 return;
1293 // Even non-http channels supply content type and content length.
1294 // Remember we don't leak header information from denied cross-site
1295 // requests. However, we handle file: and blob: URLs for blob response
1296 // types by canceling them with a specific error, so we have to allow
1297 // them to pass through this check.
1298 nsresult status;
1299 if (!mChannel || NS_FAILED(mChannel->GetStatus(&status)) ||
1300 (NS_FAILED(status) && status != NS_ERROR_FILE_ALREADY_EXISTS)) {
1301 return;
1304 // Content Type:
1305 if (header.LowerCaseEqualsASCII("content-type")) {
1306 if (!GetContentType(_retval)) {
1307 // Means no content type
1308 _retval.SetIsVoid(true);
1309 return;
1313 // Content Length:
1314 else if (header.LowerCaseEqualsASCII("content-length")) {
1315 int64_t length;
1316 if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1317 _retval.AppendInt(length);
1321 // Content Range:
1322 else if (header.LowerCaseEqualsASCII("content-range")) {
1323 GetContentRangeHeader(_retval);
1326 return;
1329 // Check for dangerous headers
1330 if (!IsSafeHeader(header, WrapNotNull(httpChannel))) {
1331 return;
1334 aRv = httpChannel->GetResponseHeader(header, _retval);
1335 if (aRv.ErrorCodeIs(NS_ERROR_NOT_AVAILABLE)) {
1336 // Means no header
1337 _retval.SetIsVoid(true);
1338 aRv.SuppressException();
1342 already_AddRefed<nsILoadGroup> XMLHttpRequestMainThread::GetLoadGroup() const {
1343 if (mFlagBackgroundRequest) {
1344 return nullptr;
1347 if (mLoadGroup) {
1348 nsCOMPtr<nsILoadGroup> ref = mLoadGroup;
1349 return ref.forget();
1352 Document* doc = GetDocumentIfCurrent();
1353 if (doc) {
1354 return doc->GetDocumentLoadGroup();
1357 return nullptr;
1360 nsresult XMLHttpRequestMainThread::FireReadystatechangeEvent() {
1361 MOZ_ASSERT(mState != XMLHttpRequest_Binding::UNSENT);
1362 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1363 event->InitEvent(kLiteralString_readystatechange, false, false);
1364 // We assume anyone who managed to call CreateReadystatechangeEvent is trusted
1365 event->SetTrusted(true);
1366 DispatchOrStoreEvent(this, event);
1367 return NS_OK;
1370 void XMLHttpRequestMainThread::DispatchProgressEvent(
1371 DOMEventTargetHelper* aTarget, const ProgressEventType& aType,
1372 int64_t aLoaded, int64_t aTotal) {
1373 DEBUG_WORKERREFS;
1374 NS_ASSERTION(aTarget, "null target");
1376 if (NS_FAILED(CheckCurrentGlobalCorrectness()) ||
1377 (!AllowUploadProgress() && aTarget == mUpload)) {
1378 return;
1381 // If blocked by CORS, zero-out the stats on progress events
1382 // and never fire "progress" or "load" events at all.
1383 if (IsDeniedCrossSiteCORSRequest()) {
1384 if (aType == Events::progress || aType == Events::load) {
1385 return;
1387 aLoaded = 0;
1388 aTotal = -1;
1391 ProgressEventInit init;
1392 init.mBubbles = false;
1393 init.mCancelable = false;
1394 init.mLengthComputable = aTotal != -1; // XHR spec step 6.1
1395 init.mLoaded = aLoaded;
1396 init.mTotal = (aTotal == -1) ? 0 : aTotal;
1398 RefPtr<ProgressEvent> event =
1399 ProgressEvent::Constructor(aTarget, aType, init);
1400 event->SetTrusted(true);
1402 MOZ_LOG(
1403 gXMLHttpRequestLog, LogLevel::Debug,
1404 ("firing %s event (%u,%u,%" PRIu64 ",%" PRIu64 ")", aType.cStr,
1405 aTarget == mUpload, aTotal != -1, aLoaded, (aTotal == -1) ? 0 : aTotal));
1407 DispatchOrStoreEvent(aTarget, event);
1409 // If we're sending a load, error, timeout or abort event, then
1410 // also dispatch the subsequent loadend event.
1411 if (aType == Events::load || aType == Events::error ||
1412 aType == Events::timeout || aType == Events::abort) {
1413 DispatchProgressEvent(aTarget, Events::loadend, aLoaded, aTotal);
1417 void XMLHttpRequestMainThread::DispatchOrStoreEvent(
1418 DOMEventTargetHelper* aTarget, Event* aEvent) {
1419 DEBUG_WORKERREFS;
1420 MOZ_ASSERT(aTarget);
1421 MOZ_ASSERT(aEvent);
1423 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
1424 return;
1427 if (mEventDispatchingSuspended) {
1428 PendingEvent* event = mPendingEvents.AppendElement();
1429 event->mTarget = aTarget;
1430 event->mEvent = aEvent;
1431 return;
1434 aTarget->DispatchEvent(*aEvent);
1437 void XMLHttpRequestMainThread::SuspendEventDispatching() {
1438 MOZ_ASSERT(!mEventDispatchingSuspended);
1439 mEventDispatchingSuspended = true;
1442 void XMLHttpRequestMainThread::ResumeEventDispatching() {
1443 MOZ_ASSERT(mEventDispatchingSuspended);
1444 mEventDispatchingSuspended = false;
1446 nsTArray<PendingEvent> pendingEvents = std::move(mPendingEvents);
1448 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
1449 return;
1452 for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
1453 pendingEvents[i].mTarget->DispatchEvent(*pendingEvents[i].mEvent);
1457 already_AddRefed<nsIHttpChannel>
1458 XMLHttpRequestMainThread::GetCurrentHttpChannel() {
1459 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
1460 return httpChannel.forget();
1463 already_AddRefed<nsIJARChannel>
1464 XMLHttpRequestMainThread::GetCurrentJARChannel() {
1465 nsCOMPtr<nsIJARChannel> appChannel = do_QueryInterface(mChannel);
1466 return appChannel.forget();
1469 bool XMLHttpRequestMainThread::IsSystemXHR() const {
1470 return mIsSystem || mPrincipal->IsSystemPrincipal();
1473 bool XMLHttpRequestMainThread::InUploadPhase() const {
1474 // We're in the upload phase while our state is OPENED.
1475 return mState == XMLHttpRequest_Binding::OPENED;
1478 // This case is hit when the async parameter is outright omitted, which
1479 // should set it to true (and the username and password to null).
1480 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1481 const nsAString& aUrl, ErrorResult& aRv) {
1482 Open(aMethod, aUrl, true, VoidString(), VoidString(), aRv);
1485 // This case is hit when the async parameter is specified, even if the
1486 // JS value was "undefined" (which due to legacy reasons should be
1487 // treated as true, which is how it will already be passed in here).
1488 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1489 const nsAString& aUrl, bool aAsync,
1490 const nsAString& aUsername,
1491 const nsAString& aPassword,
1492 ErrorResult& aRv) {
1493 Open(aMethod, NS_ConvertUTF16toUTF8(aUrl), aAsync, aUsername, aPassword, aRv);
1496 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1497 const nsACString& aUrl, bool aAsync,
1498 const nsAString& aUsername,
1499 const nsAString& aPassword,
1500 ErrorResult& aRv) {
1501 DEBUG_WORKERREFS1(aMethod << " " << aUrl);
1502 NOT_CALLABLE_IN_SYNC_SEND_RV
1504 // Gecko-specific
1505 if (!aAsync && !DontWarnAboutSyncXHR() && GetOwner() &&
1506 GetOwner()->GetExtantDoc()) {
1507 GetOwner()->GetExtantDoc()->WarnOnceAbout(
1508 DeprecatedOperations::eSyncXMLHttpRequestDeprecated);
1511 Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC,
1512 aAsync ? 0 : 1);
1514 // Step 1
1515 nsCOMPtr<Document> responsibleDocument = GetDocumentIfCurrent();
1516 if (!responsibleDocument) {
1517 // This could be because we're no longer current or because we're in some
1518 // non-window context...
1519 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
1520 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
1521 return;
1524 if (!mPrincipal) {
1525 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
1526 return;
1529 // Gecko-specific
1530 if (!aAsync && responsibleDocument && GetOwner()) {
1531 // We have no extant document during unload, so the above general
1532 // syncXHR warning will not display. But we do want to display a
1533 // recommendation to use sendBeacon instead of syncXHR during unload.
1534 nsCOMPtr<nsIDocShell> shell = responsibleDocument->GetDocShell();
1535 if (shell) {
1536 bool inUnload = false;
1537 shell->GetIsInUnload(&inUnload);
1538 if (inUnload) {
1539 LogMessage("UseSendBeaconDuringUnloadAndPagehideWarning", GetOwner());
1544 // Steps 2-4
1545 nsAutoCString method;
1546 aRv = FetchUtil::GetValidRequestMethod(aMethod, method);
1547 if (NS_WARN_IF(aRv.Failed())) {
1548 return;
1551 // Steps 5-6
1552 nsIURI* baseURI = nullptr;
1553 if (mBaseURI) {
1554 baseURI = mBaseURI;
1555 } else if (responsibleDocument) {
1556 baseURI = responsibleDocument->GetBaseURI();
1559 // Use the responsible document's encoding for the URL if we have one,
1560 // except for dedicated workers. Use UTF-8 otherwise.
1561 NotNull<const Encoding*> originCharset = UTF_8_ENCODING;
1562 if (responsibleDocument &&
1563 responsibleDocument->NodePrincipal() == mPrincipal) {
1564 originCharset = responsibleDocument->GetDocumentCharacterSet();
1567 nsCOMPtr<nsIURI> parsedURL;
1568 nsresult rv =
1569 NS_NewURI(getter_AddRefs(parsedURL), aUrl, originCharset, baseURI);
1570 if (NS_FAILED(rv)) {
1571 aRv.ThrowSyntaxError("'"_ns + aUrl + "' is not a valid URL."_ns);
1572 return;
1574 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
1575 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
1576 return;
1579 // Step 7
1580 // This is already handled by the other Open() method, which passes
1581 // username and password in as NullStrings.
1583 // Step 8
1584 nsAutoCString host;
1585 parsedURL->GetHost(host);
1586 if (!host.IsEmpty() && (!aUsername.IsVoid() || !aPassword.IsVoid())) {
1587 auto mutator = NS_MutateURI(parsedURL);
1588 if (!aUsername.IsVoid()) {
1589 mutator.SetUsername(NS_ConvertUTF16toUTF8(aUsername));
1591 if (!aPassword.IsVoid()) {
1592 mutator.SetPassword(NS_ConvertUTF16toUTF8(aPassword));
1594 Unused << mutator.Finalize(parsedURL);
1597 // Step 9
1598 if (!aAsync && HasOrHasHadOwner() &&
1599 (mTimeoutMilliseconds ||
1600 mResponseType != XMLHttpRequestResponseType::_empty)) {
1601 if (mTimeoutMilliseconds) {
1602 LogMessage("TimeoutSyncXHRWarning", GetOwner());
1604 if (mResponseType != XMLHttpRequestResponseType::_empty) {
1605 LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
1607 aRv.ThrowInvalidAccessError(
1608 "synchronous XMLHttpRequests do not support timeout and responseType");
1609 return;
1612 // Step 10
1613 TerminateOngoingFetch(NS_OK);
1615 // Step 11
1616 // timeouts are handled without a flag
1617 DisconnectDoneNotifier();
1618 mFlagSend = false;
1619 mRequestMethod.Assign(method);
1620 mRequestURL = parsedURL;
1621 mFlagSynchronous = !aAsync;
1622 mAuthorRequestHeaders.Clear();
1623 ResetResponse();
1625 // Gecko-specific
1626 mFlagHadUploadListenersOnSend = false;
1627 mFlagAborted = false;
1628 mFlagTimedOut = false;
1629 mDecoder = nullptr;
1631 // Per spec we should only create the channel on send(), but we have internal
1632 // code that relies on the channel being created now, and that code is not
1633 // always IsSystemXHR(). However, we're not supposed to throw channel-creation
1634 // errors during open(), so we silently ignore those here.
1635 CreateChannel();
1637 // Step 12
1638 if (mState != XMLHttpRequest_Binding::OPENED) {
1639 mState = XMLHttpRequest_Binding::OPENED;
1640 FireReadystatechangeEvent();
1644 void XMLHttpRequestMainThread::SetOriginAttributes(
1645 const OriginAttributesDictionary& aAttrs) {
1646 MOZ_ASSERT((mState == XMLHttpRequest_Binding::OPENED) && !mFlagSend);
1648 OriginAttributes attrs(aAttrs);
1650 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
1651 loadInfo->SetOriginAttributes(attrs);
1655 * "Copy" from a stream.
1657 nsresult XMLHttpRequestMainThread::StreamReaderFunc(
1658 nsIInputStream* in, void* closure, const char* fromRawSegment,
1659 uint32_t toOffset, uint32_t count, uint32_t* writeCount) {
1660 XMLHttpRequestMainThread* xmlHttpRequest =
1661 static_cast<XMLHttpRequestMainThread*>(closure);
1662 if (!xmlHttpRequest || !writeCount) {
1663 NS_WARNING(
1664 "XMLHttpRequest cannot read from stream: no closure or writeCount");
1665 return NS_ERROR_FAILURE;
1668 nsresult rv = NS_OK;
1670 if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Blob) {
1671 xmlHttpRequest->MaybeCreateBlobStorage();
1672 rv = xmlHttpRequest->mBlobStorage->Append(fromRawSegment, count);
1673 } else if (xmlHttpRequest->mResponseType ==
1674 XMLHttpRequestResponseType::Arraybuffer &&
1675 !xmlHttpRequest->mIsMappedArrayBuffer) {
1676 // get the initial capacity to something reasonable to avoid a bunch of
1677 // reallocs right at the start
1678 if (xmlHttpRequest->mArrayBufferBuilder->Capacity() == 0)
1679 xmlHttpRequest->mArrayBufferBuilder->SetCapacity(
1680 std::max(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE));
1682 if (NS_WARN_IF(!xmlHttpRequest->mArrayBufferBuilder->Append(
1683 reinterpret_cast<const uint8_t*>(fromRawSegment), count,
1684 XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH))) {
1685 return NS_ERROR_OUT_OF_MEMORY;
1688 } else if (xmlHttpRequest->mResponseType ==
1689 XMLHttpRequestResponseType::_empty &&
1690 xmlHttpRequest->mResponseXML) {
1691 // Copy for our own use
1692 if (!xmlHttpRequest->mResponseBody.Append(fromRawSegment, count,
1693 fallible)) {
1694 return NS_ERROR_OUT_OF_MEMORY;
1696 } else if (xmlHttpRequest->mResponseType ==
1697 XMLHttpRequestResponseType::_empty ||
1698 xmlHttpRequest->mResponseType ==
1699 XMLHttpRequestResponseType::Text ||
1700 xmlHttpRequest->mResponseType ==
1701 XMLHttpRequestResponseType::Json) {
1702 MOZ_ASSERT(!xmlHttpRequest->mResponseXML,
1703 "We shouldn't be parsing a doc here");
1704 rv = xmlHttpRequest->AppendToResponseText(
1705 AsBytes(Span(fromRawSegment, count)));
1706 if (NS_WARN_IF(NS_FAILED(rv))) {
1707 return rv;
1711 if (xmlHttpRequest->mFlagParseBody) {
1712 // Give the same data to the parser.
1714 // We need to wrap the data in a new lightweight stream and pass that
1715 // to the parser, because calling ReadSegments() recursively on the same
1716 // stream is not supported.
1717 nsCOMPtr<nsIInputStream> copyStream;
1718 rv = NS_NewByteInputStream(getter_AddRefs(copyStream),
1719 Span(fromRawSegment, count),
1720 NS_ASSIGNMENT_DEPEND);
1722 if (NS_SUCCEEDED(rv) && xmlHttpRequest->mXMLParserStreamListener) {
1723 NS_ASSERTION(copyStream, "NS_NewByteInputStream lied");
1724 nsresult parsingResult =
1725 xmlHttpRequest->mXMLParserStreamListener->OnDataAvailable(
1726 xmlHttpRequest->mChannel, copyStream, toOffset, count);
1728 // No use to continue parsing if we failed here, but we
1729 // should still finish reading the stream
1730 if (NS_FAILED(parsingResult)) {
1731 xmlHttpRequest->mFlagParseBody = false;
1736 if (NS_SUCCEEDED(rv)) {
1737 *writeCount = count;
1738 } else {
1739 *writeCount = 0;
1742 return rv;
1745 namespace {
1747 void GetBlobURIFromChannel(nsIRequest* aRequest, nsIURI** aURI) {
1748 MOZ_ASSERT(aRequest);
1749 MOZ_ASSERT(aURI);
1751 *aURI = nullptr;
1753 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
1754 if (!channel) {
1755 return;
1758 nsCOMPtr<nsIURI> uri;
1759 nsresult rv = channel->GetURI(getter_AddRefs(uri));
1760 if (NS_FAILED(rv)) {
1761 return;
1764 if (!dom::IsBlobURI(uri)) {
1765 return;
1768 uri.forget(aURI);
1771 nsresult GetLocalFileFromChannel(nsIRequest* aRequest, nsIFile** aFile) {
1772 MOZ_ASSERT(aRequest);
1773 MOZ_ASSERT(aFile);
1775 *aFile = nullptr;
1777 nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aRequest);
1778 if (!fc) {
1779 return NS_OK;
1782 nsCOMPtr<nsIFile> file;
1783 nsresult rv = fc->GetFile(getter_AddRefs(file));
1784 if (NS_WARN_IF(NS_FAILED(rv))) {
1785 return rv;
1788 file.forget(aFile);
1789 return NS_OK;
1792 nsresult DummyStreamReaderFunc(nsIInputStream* aInputStream, void* aClosure,
1793 const char* aFromRawSegment, uint32_t aToOffset,
1794 uint32_t aCount, uint32_t* aWriteCount) {
1795 *aWriteCount = aCount;
1796 return NS_OK;
1799 class FileCreationHandler final : public PromiseNativeHandler {
1800 public:
1801 NS_DECL_ISUPPORTS
1803 static void Create(Promise* aPromise, XMLHttpRequestMainThread* aXHR) {
1804 MOZ_ASSERT(aPromise);
1806 RefPtr<FileCreationHandler> handler = new FileCreationHandler(aXHR);
1807 aPromise->AppendNativeHandler(handler);
1810 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
1811 ErrorResult& aRv) override {
1812 if (NS_WARN_IF(!aValue.isObject())) {
1813 mXHR->LocalFileToBlobCompleted(nullptr);
1814 return;
1817 RefPtr<Blob> blob;
1818 if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) {
1819 mXHR->LocalFileToBlobCompleted(nullptr);
1820 return;
1823 mXHR->LocalFileToBlobCompleted(blob->Impl());
1826 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
1827 ErrorResult& aRv) override {
1828 mXHR->LocalFileToBlobCompleted(nullptr);
1831 private:
1832 explicit FileCreationHandler(XMLHttpRequestMainThread* aXHR) : mXHR(aXHR) {
1833 MOZ_ASSERT(aXHR);
1836 ~FileCreationHandler() = default;
1838 RefPtr<XMLHttpRequestMainThread> mXHR;
1841 NS_IMPL_ISUPPORTS0(FileCreationHandler)
1843 } // namespace
1845 void XMLHttpRequestMainThread::LocalFileToBlobCompleted(BlobImpl* aBlobImpl) {
1846 MOZ_ASSERT(mState != XMLHttpRequest_Binding::DONE);
1848 mResponseBlobImpl = aBlobImpl;
1849 mBlobStorage = nullptr;
1850 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1852 ChangeStateToDone(mFlagSyncLooping);
1855 NS_IMETHODIMP
1856 XMLHttpRequestMainThread::OnDataAvailable(nsIRequest* request,
1857 nsIInputStream* inStr,
1858 uint64_t sourceOffset,
1859 uint32_t count) {
1860 DEBUG_WORKERREFS;
1861 NS_ENSURE_ARG_POINTER(inStr);
1863 mProgressSinceLastProgressEvent = true;
1864 XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
1866 nsresult rv;
1868 if (mResponseType == XMLHttpRequestResponseType::Blob) {
1869 nsCOMPtr<nsIFile> localFile;
1870 nsCOMPtr<nsIURI> blobURI;
1871 GetBlobURIFromChannel(request, getter_AddRefs(blobURI));
1872 if (blobURI) {
1873 RefPtr<BlobImpl> blobImpl;
1874 rv = NS_GetBlobForBlobURI(blobURI, getter_AddRefs(blobImpl));
1875 if (NS_SUCCEEDED(rv)) {
1876 mResponseBlobImpl = blobImpl;
1878 } else {
1879 rv = GetLocalFileFromChannel(request, getter_AddRefs(localFile));
1881 if (NS_WARN_IF(NS_FAILED(rv))) {
1882 return rv;
1885 if (mResponseBlobImpl || localFile) {
1886 mBlobStorage = nullptr;
1887 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1889 // The nsIStreamListener contract mandates us to read from the stream
1890 // before returning.
1891 uint32_t totalRead;
1892 rv = inStr->ReadSegments(DummyStreamReaderFunc, nullptr, count,
1893 &totalRead);
1894 NS_ENSURE_SUCCESS(rv, rv);
1896 ChangeState(XMLHttpRequest_Binding::LOADING);
1898 // Cancel() must be called with an error. We use
1899 // NS_ERROR_FILE_ALREADY_EXISTS to know that we've aborted the operation
1900 // just because we can retrieve the File from the channel directly.
1901 return request->Cancel(NS_ERROR_FILE_ALREADY_EXISTS);
1905 uint32_t totalRead;
1906 rv = inStr->ReadSegments(XMLHttpRequestMainThread::StreamReaderFunc,
1907 (void*)this, count, &totalRead);
1908 NS_ENSURE_SUCCESS(rv, rv);
1910 // Fire the first progress event/loading state change
1911 if (mState == XMLHttpRequest_Binding::HEADERS_RECEIVED) {
1912 ChangeState(XMLHttpRequest_Binding::LOADING);
1913 if (!mFlagSynchronous) {
1914 DispatchProgressEvent(this, Events::progress, mLoadTransferred,
1915 mLoadTotal);
1917 mProgressSinceLastProgressEvent = false;
1920 if (!mFlagSynchronous && !mProgressTimerIsActive) {
1921 StartProgressEventTimer();
1924 return NS_OK;
1927 NS_IMETHODIMP
1928 XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) {
1929 DEBUG_WORKERREFS;
1930 AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStartRequest", NETWORK);
1932 nsresult rv = NS_OK;
1934 if (request != mChannel) {
1935 // Can this still happen?
1936 return NS_OK;
1939 // Don't do anything if we have been aborted
1940 if (mState == XMLHttpRequest_Binding::UNSENT) {
1941 return NS_OK;
1944 // Don't do anything if we're in mid-abort, but let the request
1945 // know (this can happen due to race conditions in valid XHRs,
1946 // see bz1070763 for info).
1947 if (mFlagAborted) {
1948 return NS_BINDING_ABORTED;
1951 // Don't do anything if we have timed out.
1952 if (mFlagTimedOut) {
1953 return NS_OK;
1956 // If we were asked for a bad range on a blob URL, but we're async,
1957 // we should throw now in order to fire an error progress event.
1958 if (IsBlobURI(mRequestURL) && GetRequestedContentRange().isNothing() &&
1959 mAuthorRequestHeaders.Has("range")) {
1960 return NS_ERROR_NET_PARTIAL_TRANSFER;
1963 nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
1964 NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
1966 nsresult status;
1967 request->GetStatus(&status);
1968 if (mErrorLoad == ErrorType::eOK && NS_FAILED(status)) {
1969 mErrorLoad = ErrorType::eRequest;
1970 mErrorLoadDetail = status;
1973 // Upload phase is now over. If we were uploading anything,
1974 // stop the timer and fire any final progress events.
1975 if (mUpload && !mUploadComplete && mErrorLoad == ErrorType::eOK &&
1976 !mFlagSynchronous) {
1977 StopProgressEventTimer();
1979 mUploadTransferred = mUploadTotal;
1981 if (mProgressSinceLastProgressEvent) {
1982 DispatchProgressEvent(mUpload, Events::progress, mUploadTransferred,
1983 mUploadTotal);
1984 mProgressSinceLastProgressEvent = false;
1987 mUploadComplete = true;
1988 DispatchProgressEvent(mUpload, Events::load, mUploadTotal, mUploadTotal);
1991 mFlagParseBody = true;
1992 if (mErrorLoad == ErrorType::eOK) {
1993 ChangeState(XMLHttpRequest_Binding::HEADERS_RECEIVED);
1996 ResetResponse();
1998 if (!mOverrideMimeType.IsEmpty()) {
1999 channel->SetContentType(NS_ConvertUTF16toUTF8(mOverrideMimeType));
2002 // Fallback to 'application/octet-stream' (leaving data URLs alone)
2003 if (!IsBlobURI(mRequestURL)) {
2004 nsAutoCString type;
2005 channel->GetContentType(type);
2006 if (type.IsEmpty() || type.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
2007 channel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
2011 DetectCharset();
2013 // Set up arraybuffer
2014 if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
2015 NS_SUCCEEDED(status)) {
2016 if (mIsMappedArrayBuffer) {
2017 nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
2018 if (jarChannel) {
2019 nsCOMPtr<nsIURI> uri;
2020 rv = channel->GetURI(getter_AddRefs(uri));
2021 if (NS_SUCCEEDED(rv)) {
2022 nsAutoCString file;
2023 nsAutoCString scheme;
2024 uri->GetScheme(scheme);
2025 if (scheme.LowerCaseEqualsLiteral("jar")) {
2026 nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri);
2027 if (jarURI) {
2028 jarURI->GetJAREntry(file);
2031 nsCOMPtr<nsIFile> jarFile;
2032 jarChannel->GetJarFile(getter_AddRefs(jarFile));
2033 if (!jarFile) {
2034 mIsMappedArrayBuffer = false;
2035 } else {
2036 rv = mArrayBufferBuilder->MapToFileInPackage(file, jarFile);
2037 // This can happen legitimately if there are compressed files
2038 // in the jarFile. See bug #1357219. No need to warn on the error.
2039 if (NS_FAILED(rv)) {
2040 mIsMappedArrayBuffer = false;
2041 } else {
2042 channel->SetContentType("application/mem-mapped"_ns);
2048 // If memory mapping failed, mIsMappedArrayBuffer would be set to false,
2049 // and we want it fallback to the malloc way.
2050 if (!mIsMappedArrayBuffer) {
2051 int64_t contentLength;
2052 rv = channel->GetContentLength(&contentLength);
2053 if (NS_SUCCEEDED(rv) && contentLength > 0 &&
2054 contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) {
2055 mArrayBufferBuilder->SetCapacity(static_cast<int32_t>(contentLength));
2060 // Set up responseXML
2061 // Fetch spec Main Fetch step 21: ignore body for head/connect methods.
2062 bool parseBody = (mResponseType == XMLHttpRequestResponseType::_empty ||
2063 mResponseType == XMLHttpRequestResponseType::Document) &&
2064 !(mRequestMethod.EqualsLiteral("HEAD") ||
2065 mRequestMethod.EqualsLiteral("CONNECT"));
2067 if (parseBody) {
2068 // Do not try to parse documents if content-length = 0
2069 int64_t contentLength;
2070 if (NS_SUCCEEDED(mChannel->GetContentLength(&contentLength)) &&
2071 contentLength == 0) {
2072 parseBody = false;
2076 mIsHtml = false;
2077 mWarnAboutSyncHtml = false;
2078 if (parseBody && NS_SUCCEEDED(status)) {
2079 // We can gain a huge performance win by not even trying to
2080 // parse non-XML data. This also protects us from the situation
2081 // where we have an XML document and sink, but HTML (or other)
2082 // parser, which can produce unreliable results.
2083 nsAutoCString type;
2084 channel->GetContentType(type);
2086 if ((mResponseType == XMLHttpRequestResponseType::Document) &&
2087 type.EqualsLiteral("text/html")) {
2088 // HTML parsing is only supported for responseType == "document" to
2089 // avoid running the parser and, worse, populating responseXML for
2090 // legacy users of XHR who use responseType == "" for retrieving the
2091 // responseText of text/html resources. This legacy case is so common
2092 // that it's not useful to emit a warning about it.
2093 if (mFlagSynchronous) {
2094 // We don't make cool new features available in the bad synchronous
2095 // mode. The synchronous mode is for legacy only.
2096 mWarnAboutSyncHtml = true;
2097 mFlagParseBody = false;
2098 } else {
2099 mIsHtml = true;
2101 } else if (!type.IsEmpty() && (!(type.EqualsLiteral("text/xml") ||
2102 type.EqualsLiteral("application/xml") ||
2103 StringEndsWith(type, "+xml"_ns)))) {
2104 // Follow https://xhr.spec.whatwg.org/
2105 // If final MIME type is not null, text/html, text/xml, application/xml,
2106 // or does not end in +xml, return null.
2107 mFlagParseBody = false;
2109 } else {
2110 // The request failed, so we shouldn't be parsing anyway
2111 mFlagParseBody = false;
2114 if (mFlagParseBody) {
2115 nsCOMPtr<nsIURI> baseURI, docURI;
2116 rv = mChannel->GetURI(getter_AddRefs(docURI));
2117 NS_ENSURE_SUCCESS(rv, rv);
2118 baseURI = docURI;
2120 nsCOMPtr<Document> doc = GetDocumentIfCurrent();
2121 nsCOMPtr<nsIURI> chromeXHRDocURI, chromeXHRDocBaseURI;
2122 if (doc) {
2123 chromeXHRDocURI = doc->GetDocumentURI();
2124 chromeXHRDocBaseURI = doc->GetBaseURI();
2125 } else {
2126 // If we're no longer current, just kill the load, though it really should
2127 // have been killed already.
2128 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
2129 return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
2133 // Create an empty document from it.
2134 const auto& emptyStr = u""_ns;
2135 nsIGlobalObject* global = DOMEventTargetHelper::GetParentObject();
2137 nsCOMPtr<nsIPrincipal> requestingPrincipal;
2138 rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
2139 channel, getter_AddRefs(requestingPrincipal));
2140 NS_ENSURE_SUCCESS(rv, rv);
2142 rv = NS_NewDOMDocument(
2143 getter_AddRefs(mResponseXML), emptyStr, emptyStr, nullptr, docURI,
2144 baseURI, requestingPrincipal, true, global,
2145 mIsHtml ? DocumentFlavorHTML : DocumentFlavorLegacyGuess);
2146 NS_ENSURE_SUCCESS(rv, rv);
2147 mResponseXML->SetChromeXHRDocURI(chromeXHRDocURI);
2148 mResponseXML->SetChromeXHRDocBaseURI(chromeXHRDocBaseURI);
2150 // suppress parsing failure messages to console for statuses which
2151 // can have empty bodies (see bug 884693).
2152 IgnoredErrorResult rv2;
2153 uint32_t responseStatus = GetStatus(rv2);
2154 if (!rv2.Failed() && (responseStatus == 201 || responseStatus == 202 ||
2155 responseStatus == 204 || responseStatus == 205 ||
2156 responseStatus == 304)) {
2157 mResponseXML->SetSuppressParserErrorConsoleMessages(true);
2160 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2161 bool isCrossSite = false;
2162 isCrossSite = loadInfo->GetTainting() != LoadTainting::Basic;
2164 if (isCrossSite) {
2165 mResponseXML->DisableCookieAccess();
2168 nsCOMPtr<nsIStreamListener> listener;
2169 nsCOMPtr<nsILoadGroup> loadGroup;
2170 channel->GetLoadGroup(getter_AddRefs(loadGroup));
2172 // suppress <parsererror> nodes on XML document parse failure, but only
2173 // for non-privileged code (including Web Extensions). See bug 289714.
2174 if (!IsSystemXHR()) {
2175 mResponseXML->SetSuppressParserErrorElement(true);
2178 rv = mResponseXML->StartDocumentLoad(kLoadAsData, channel, loadGroup,
2179 nullptr, getter_AddRefs(listener),
2180 !isCrossSite);
2181 NS_ENSURE_SUCCESS(rv, rv);
2183 // the spec requires the response document.referrer to be the empty string
2184 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2185 new ReferrerInfo(nullptr, mResponseXML->ReferrerPolicy());
2186 mResponseXML->SetReferrerInfo(referrerInfo);
2188 mXMLParserStreamListener = listener;
2189 rv = mXMLParserStreamListener->OnStartRequest(request);
2190 NS_ENSURE_SUCCESS(rv, rv);
2193 // Download phase beginning; start the progress event timer if necessary.
2194 if (NS_SUCCEEDED(rv) && HasListenersFor(nsGkAtoms::onprogress)) {
2195 StartProgressEventTimer();
2198 return NS_OK;
2201 NS_IMETHODIMP
2202 XMLHttpRequestMainThread::OnStopRequest(nsIRequest* request, nsresult status) {
2203 DEBUG_WORKERREFS;
2204 AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStopRequest", NETWORK);
2206 if (request != mChannel) {
2207 // Can this still happen?
2208 return NS_OK;
2211 // Send the decoder the signal that we've hit the end of the stream,
2212 // but only when decoding text eagerly.
2213 if (mDecoder && ((mResponseType == XMLHttpRequestResponseType::Text) ||
2214 (mResponseType == XMLHttpRequestResponseType::Json) ||
2215 (mResponseType == XMLHttpRequestResponseType::_empty &&
2216 !mResponseXML))) {
2217 AppendToResponseText(Span<const uint8_t>(), true);
2220 mWaitingForOnStopRequest = false;
2222 // make sure to notify the listener if we were aborted
2223 // XXX in fact, why don't we do the cleanup below in this case??
2224 // UNSENT is for abort calls. See OnStartRequest above.
2225 if (mState == XMLHttpRequest_Binding::UNSENT || mFlagTimedOut) {
2226 if (mXMLParserStreamListener)
2227 (void)mXMLParserStreamListener->OnStopRequest(request, status);
2228 return NS_OK;
2231 // Is this good enough here?
2232 if (mXMLParserStreamListener && mFlagParseBody) {
2233 mXMLParserStreamListener->OnStopRequest(request, status);
2236 mXMLParserStreamListener = nullptr;
2237 mContext = nullptr;
2239 // If window.stop() or other aborts were issued, handle as an abort
2240 if (status == NS_BINDING_ABORTED) {
2241 mFlagParseBody = false;
2242 IgnoredErrorResult rv;
2243 RequestErrorSteps(Events::abort, NS_ERROR_DOM_ABORT_ERR, rv);
2244 ChangeState(XMLHttpRequest_Binding::UNSENT, false);
2245 return NS_OK;
2248 // If we were just reading a blob URL, we're already done
2249 if (status == NS_ERROR_FILE_ALREADY_EXISTS && mResponseBlobImpl) {
2250 ChangeStateToDone(mFlagSyncLooping);
2251 return NS_OK;
2254 bool waitingForBlobCreation = false;
2256 // If we have this error, we have to deal with a file: URL + responseType =
2257 // blob. We have this error because we canceled the channel. The status will
2258 // be set to NS_OK.
2259 if (!mResponseBlobImpl && status == NS_ERROR_FILE_ALREADY_EXISTS &&
2260 mResponseType == XMLHttpRequestResponseType::Blob) {
2261 nsCOMPtr<nsIFile> file;
2262 nsresult rv = GetLocalFileFromChannel(request, getter_AddRefs(file));
2263 if (NS_WARN_IF(NS_FAILED(rv))) {
2264 return rv;
2267 if (file) {
2268 nsAutoCString contentType;
2269 rv = mChannel->GetContentType(contentType);
2270 if (NS_WARN_IF(NS_FAILED(rv))) {
2271 return rv;
2274 ChromeFilePropertyBag bag;
2275 CopyUTF8toUTF16(contentType, bag.mType);
2277 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
2279 ErrorResult error;
2280 RefPtr<Promise> promise =
2281 FileCreatorHelper::CreateFile(global, file, bag, true, error);
2282 if (NS_WARN_IF(error.Failed())) {
2283 return error.StealNSResult();
2286 FileCreationHandler::Create(promise, this);
2287 waitingForBlobCreation = true;
2288 status = NS_OK;
2290 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
2291 NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
2295 if (NS_SUCCEEDED(status) &&
2296 mResponseType == XMLHttpRequestResponseType::Blob &&
2297 !waitingForBlobCreation) {
2298 // Smaller files may be written in cache map instead of separate files.
2299 // Also, no-store response cannot be written in persistent cache.
2300 nsAutoCString contentType;
2301 if (!mOverrideMimeType.IsEmpty()) {
2302 contentType.Assign(NS_ConvertUTF16toUTF8(mOverrideMimeType));
2303 } else {
2304 mChannel->GetContentType(contentType);
2307 // mBlobStorage can be null if the channel is non-file non-cacheable
2308 // and if the response length is zero.
2309 MaybeCreateBlobStorage();
2310 mBlobStorage->GetBlobImplWhenReady(contentType, this);
2311 waitingForBlobCreation = true;
2313 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
2314 NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
2315 } else if (NS_SUCCEEDED(status) && !mIsMappedArrayBuffer &&
2316 mResponseType == XMLHttpRequestResponseType::Arraybuffer) {
2317 // set the capacity down to the actual length, to realloc back
2318 // down to the actual size
2319 if (!mArrayBufferBuilder->SetCapacity(mArrayBufferBuilder->Length())) {
2320 // this should never happen!
2321 status = NS_ERROR_UNEXPECTED;
2325 nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
2326 NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
2328 channel->SetNotificationCallbacks(nullptr);
2329 mNotificationCallbacks = nullptr;
2330 mChannelEventSink = nullptr;
2331 mProgressEventSink = nullptr;
2333 bool wasSync = mFlagSyncLooping;
2334 mFlagSyncLooping = false;
2335 mRequestSentTime = 0;
2337 // update our charset and decoder to match mResponseXML,
2338 // before it is possibly nulled out
2339 MatchCharsetAndDecoderToResponseDocument();
2341 if (NS_FAILED(status)) {
2342 // This can happen if the server is unreachable. Other possible
2343 // reasons are that the user leaves the page or hits the ESC key.
2345 mErrorLoad = ErrorType::eUnreachable;
2346 mErrorLoadDetail = status;
2347 mResponseXML = nullptr;
2349 // Handle network errors specifically per spec.
2350 if (NS_ERROR_GET_MODULE(status) == NS_ERROR_MODULE_NETWORK) {
2351 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
2352 ("%p detected networking error 0x%" PRIx32 "\n", this,
2353 static_cast<uint32_t>(status)));
2354 IgnoredErrorResult rv;
2355 mFlagParseBody = false;
2356 RequestErrorSteps(Events::error, NS_ERROR_DOM_NETWORK_ERR, rv);
2357 // RequestErrorSteps will not call ChangeStateToDone for sync XHRs, so we
2358 // do so here to ensure progress events are sent and our state is sane.
2359 if (mFlagSynchronous) {
2360 ChangeStateToDone(wasSync);
2362 return NS_OK;
2365 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
2366 ("%p detected unreachable error 0x%" PRIx32 "\n", this,
2367 static_cast<uint32_t>(status)));
2370 // If we're uninitialized at this point, we encountered an error
2371 // earlier and listeners have already been notified. Also we do
2372 // not want to do this if we already completed.
2373 if (mState == XMLHttpRequest_Binding::UNSENT ||
2374 mState == XMLHttpRequest_Binding::DONE) {
2375 return NS_OK;
2378 if (!mResponseXML) {
2379 mFlagParseBody = false;
2381 // We postpone the 'done' until the creation of the Blob is completed.
2382 if (!waitingForBlobCreation) {
2383 ChangeStateToDone(wasSync);
2386 return NS_OK;
2389 if (mIsHtml) {
2390 NS_ASSERTION(!mFlagSyncLooping,
2391 "We weren't supposed to support HTML parsing with XHR!");
2392 mParseEndListener = new nsXHRParseEndListener(this);
2393 RefPtr<EventTarget> eventTarget = mResponseXML;
2394 EventListenerManager* manager = eventTarget->GetOrCreateListenerManager();
2395 manager->AddEventListenerByType(mParseEndListener,
2396 kLiteralString_DOMContentLoaded,
2397 TrustedEventsAtSystemGroupBubble());
2398 return NS_OK;
2399 } else {
2400 mFlagParseBody = false;
2403 // We might have been sent non-XML data. If that was the case,
2404 // we should null out the document member. The idea in this
2405 // check here is that if there is no document element it is not
2406 // an XML document. We might need a fancier check...
2407 if (!mResponseXML->GetRootElement()) {
2408 mErrorParsingXML = true;
2409 mResponseXML = nullptr;
2411 ChangeStateToDone(wasSync);
2412 return NS_OK;
2415 void XMLHttpRequestMainThread::OnBodyParseEnd() {
2416 mFlagParseBody = false;
2417 mParseEndListener = nullptr;
2418 ChangeStateToDone(mFlagSyncLooping);
2421 void XMLHttpRequestMainThread::MatchCharsetAndDecoderToResponseDocument() {
2422 if (mResponseXML &&
2423 (!mDecoder ||
2424 mDecoder->Encoding() != mResponseXML->GetDocumentCharacterSet())) {
2425 TruncateResponseText();
2426 mResponseBodyDecodedPos = 0;
2427 mEofDecoded = false;
2428 mDecoder = mResponseXML->GetDocumentCharacterSet()->NewDecoder();
2432 void XMLHttpRequestMainThread::DisconnectDoneNotifier() {
2433 if (mDelayedDoneNotifier) {
2434 // Disconnect may release the last reference to 'this'.
2435 RefPtr<XMLHttpRequestMainThread> kungfuDeathGrip = this;
2436 mDelayedDoneNotifier->Disconnect();
2437 mDelayedDoneNotifier = nullptr;
2441 void XMLHttpRequestMainThread::ChangeStateToDone(bool aWasSync) {
2442 DEBUG_WORKERREFS;
2443 DisconnectDoneNotifier();
2445 if (!mForWorker && !aWasSync && mChannel) {
2446 // If the top level page is loading, try to postpone the handling of the
2447 // final events.
2448 nsLoadFlags loadFlags = 0;
2449 mChannel->GetLoadFlags(&loadFlags);
2450 if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
2451 nsPIDOMWindowInner* owner = GetOwner();
2452 BrowsingContext* bc = owner ? owner->GetBrowsingContext() : nullptr;
2453 bc = bc ? bc->Top() : nullptr;
2454 if (bc && bc->IsLoading()) {
2455 MOZ_ASSERT(!mDelayedDoneNotifier);
2456 RefPtr<XMLHttpRequestDoneNotifier> notifier =
2457 new XMLHttpRequestDoneNotifier(this);
2458 mDelayedDoneNotifier = notifier;
2459 bc->AddDeprioritizedLoadRunner(notifier);
2460 return;
2465 ChangeStateToDoneInternal();
2468 void XMLHttpRequestMainThread::ChangeStateToDoneInternal() {
2469 DEBUG_WORKERREFS;
2470 DisconnectDoneNotifier();
2471 StopProgressEventTimer();
2473 MOZ_ASSERT(!mFlagParseBody,
2474 "ChangeStateToDone() called before async HTML parsing is done.");
2476 mFlagSend = false;
2478 CancelTimeoutTimer();
2480 // Per spec, fire the last download progress event, if any,
2481 // before readystatechange=4/done. (Note that 0-sized responses
2482 // will have not sent a progress event yet, so one must be sent here).
2483 if (!mFlagSynchronous &&
2484 (!mLoadTransferred || mProgressSinceLastProgressEvent)) {
2485 DispatchProgressEvent(this, Events::progress, mLoadTransferred, mLoadTotal);
2486 mProgressSinceLastProgressEvent = false;
2489 // Notify the document when an XHR request completes successfully.
2490 // This is used by the password manager as a hint to observe DOM mutations.
2491 // Call this prior to changing state to DONE to ensure we set up the
2492 // observer before mutations occur.
2493 if (mErrorLoad == ErrorType::eOK) {
2494 Document* doc = GetDocumentIfCurrent();
2495 if (doc) {
2496 doc->NotifyFetchOrXHRSuccess();
2500 // Per spec, fire readystatechange=4/done before final error events.
2501 ChangeState(XMLHttpRequest_Binding::DONE, true);
2503 // Per spec, if we failed in the upload phase, fire a final error
2504 // and loadend events for the upload after readystatechange=4/done.
2505 if (!mFlagSynchronous && mUpload && !mUploadComplete) {
2506 DispatchProgressEvent(mUpload, Events::error, 0, -1);
2509 // Per spec, fire download's load/error and loadend events after
2510 // readystatechange=4/done (and of course all upload events).
2511 if (mErrorLoad != ErrorType::eOK) {
2512 DispatchProgressEvent(this, Events::error, 0, -1);
2513 } else {
2514 DispatchProgressEvent(this, Events::load, mLoadTransferred, mLoadTotal);
2517 if (mErrorLoad != ErrorType::eOK) {
2518 // By nulling out channel here we make it so that Send() can test
2519 // for that and throw. Also calling the various status
2520 // methods/members will not throw.
2521 // This matches what IE does.
2522 mChannel = nullptr;
2526 nsresult XMLHttpRequestMainThread::CreateChannel() {
2527 DEBUG_WORKERREFS;
2528 // When we are called from JS we can find the load group for the page,
2529 // and add ourselves to it. This way any pending requests
2530 // will be automatically aborted if the user leaves the page.
2531 nsCOMPtr<nsILoadGroup> loadGroup = GetLoadGroup();
2533 nsSecurityFlags secFlags;
2534 nsLoadFlags loadFlags = nsIRequest::LOAD_BACKGROUND;
2535 uint32_t sandboxFlags = 0;
2536 if (mPrincipal->IsSystemPrincipal()) {
2537 // When chrome is loading we want to make sure to sandbox any potential
2538 // result document. We also want to allow cross-origin loads.
2539 secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
2540 sandboxFlags = SANDBOXED_ORIGIN;
2541 } else if (IsSystemXHR()) {
2542 // For pages that have appropriate permissions, we want to still allow
2543 // cross-origin loads, but make sure that the any potential result
2544 // documents get the same principal as the loader.
2545 secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
2546 nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
2547 loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
2548 } else {
2549 // Otherwise use CORS. Again, make sure that potential result documents
2550 // use the same principal as the loader.
2551 secFlags = nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT |
2552 nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
2555 if (mIsAnon) {
2556 secFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
2559 // Use the responsibleDocument if we have it, except for dedicated workers
2560 // where it will be the parent document, which is not the one we want to use.
2561 nsresult rv;
2562 nsCOMPtr<Document> responsibleDocument = GetDocumentIfCurrent();
2563 if (responsibleDocument &&
2564 responsibleDocument->NodePrincipal() == mPrincipal) {
2565 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL,
2566 responsibleDocument, secFlags,
2567 nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2568 nullptr, // aPerformanceStorage
2569 loadGroup,
2570 nullptr, // aCallbacks
2571 loadFlags, nullptr, sandboxFlags);
2572 } else if (mClientInfo.isSome()) {
2573 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
2574 mClientInfo.ref(), mController, secFlags,
2575 nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2576 mCookieJarSettings,
2577 mPerformanceStorage, // aPerformanceStorage
2578 loadGroup,
2579 nullptr, // aCallbacks
2580 loadFlags, nullptr, sandboxFlags);
2581 } else {
2582 // Otherwise use the principal.
2583 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
2584 secFlags, nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2585 mCookieJarSettings,
2586 mPerformanceStorage, // aPerformanceStorage
2587 loadGroup,
2588 nullptr, // aCallbacks
2589 loadFlags, nullptr, sandboxFlags);
2591 NS_ENSURE_SUCCESS(rv, rv);
2593 if (mCSPEventListener) {
2594 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2595 rv = loadInfo->SetCspEventListener(mCSPEventListener);
2596 NS_ENSURE_SUCCESS(rv, rv);
2599 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2600 if (httpChannel) {
2601 rv = httpChannel->SetRequestMethod(mRequestMethod);
2602 NS_ENSURE_SUCCESS(rv, rv);
2604 httpChannel->SetSource(profiler_capture_backtrace());
2606 // Set the initiator type
2607 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
2608 if (timedChannel) {
2609 timedChannel->SetInitiatorType(u"xmlhttprequest"_ns);
2613 return NS_OK;
2616 void XMLHttpRequestMainThread::MaybeLowerChannelPriority() {
2617 nsCOMPtr<Document> doc = GetDocumentIfCurrent();
2618 if (!doc) {
2619 return;
2622 AutoJSAPI jsapi;
2623 if (!jsapi.Init(GetOwnerGlobal())) {
2624 return;
2627 JSContext* cx = jsapi.cx();
2629 if (!doc->IsScriptTracking(cx)) {
2630 return;
2633 if (StaticPrefs::network_http_tailing_enabled()) {
2634 nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel);
2635 if (cos) {
2636 // Adding TailAllowed to overrule the Unblocked flag, but to preserve
2637 // the effect of Unblocked when tailing is off.
2638 cos->AddClassFlags(nsIClassOfService::Throttleable |
2639 nsIClassOfService::Tail |
2640 nsIClassOfService::TailAllowed);
2644 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
2645 if (p) {
2646 p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
2650 nsresult XMLHttpRequestMainThread::InitiateFetch(
2651 already_AddRefed<nsIInputStream> aUploadStream, int64_t aUploadLength,
2652 nsACString& aUploadContentType) {
2653 DEBUG_WORKERREFS;
2654 nsresult rv;
2655 nsCOMPtr<nsIInputStream> uploadStream = std::move(aUploadStream);
2657 if (!uploadStream) {
2658 RefPtr<PreloaderBase> preload = FindPreload();
2659 if (preload) {
2660 // Because of bug 682305, we can't let listener be the XHR object itself
2661 // because JS wouldn't be able to use it. So create a listener around
2662 // 'this'. Make sure to hold a strong reference so that we don't leak the
2663 // wrapper.
2664 nsCOMPtr<nsIStreamListener> listener =
2665 new net::nsStreamListenerWrapper(this);
2666 rv = preload->AsyncConsume(listener);
2667 if (NS_SUCCEEDED(rv)) {
2668 mFromPreload = true;
2670 // May be null when the preload has already finished, but the XHR code
2671 // is safe to live with it.
2672 mChannel = preload->Channel();
2673 MOZ_ASSERT(mChannel);
2674 EnsureChannelContentType();
2675 return NS_OK;
2678 preload = nullptr;
2682 // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which
2683 // in turn keeps STOP button from becoming active. If the consumer passed in
2684 // a progress event handler we must load with nsIRequest::LOAD_NORMAL or
2685 // necko won't generate any progress notifications.
2686 if (HasListenersFor(nsGkAtoms::onprogress) ||
2687 (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress))) {
2688 nsLoadFlags loadFlags;
2689 mChannel->GetLoadFlags(&loadFlags);
2690 loadFlags &= ~nsIRequest::LOAD_BACKGROUND;
2691 loadFlags |= nsIRequest::LOAD_NORMAL;
2692 mChannel->SetLoadFlags(loadFlags);
2695 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2696 if (httpChannel) {
2697 // If the user hasn't overridden the Accept header, set it to */* per spec.
2698 if (!mAuthorRequestHeaders.Has("accept")) {
2699 mAuthorRequestHeaders.Set("accept", "*/*"_ns);
2702 mAuthorRequestHeaders.ApplyToChannel(httpChannel, false, false);
2704 if (!IsSystemXHR()) {
2705 nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
2706 nsCOMPtr<Document> doc = owner ? owner->GetExtantDoc() : nullptr;
2707 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2708 ReferrerInfo::CreateForFetch(mPrincipal, doc);
2709 Unused << httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
2712 // Some extensions override the http protocol handler and provide their own
2713 // implementation. The channels returned from that implementation don't
2714 // always seem to implement the nsIUploadChannel2 interface, presumably
2715 // because it's a new interface. Eventually we should remove this and simply
2716 // require that http channels implement the new interface (see bug 529041).
2717 nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
2718 if (!uploadChannel2) {
2719 nsCOMPtr<nsIConsoleService> consoleService =
2720 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
2721 if (consoleService) {
2722 consoleService->LogStringMessage(
2723 u"Http channel implementation doesn't support nsIUploadChannel2. "
2724 "An extension has supplied a non-functional http protocol handler. "
2725 "This will break behavior and in future releases not work at all.");
2729 if (uploadStream) {
2730 // If necessary, wrap the stream in a buffered stream so as to guarantee
2731 // support for our upload when calling ExplicitSetUploadStream.
2732 if (!NS_InputStreamIsBuffered(uploadStream)) {
2733 nsCOMPtr<nsIInputStream> bufferedStream;
2734 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
2735 uploadStream.forget(), 4096);
2736 NS_ENSURE_SUCCESS(rv, rv);
2738 uploadStream = bufferedStream;
2741 // We want to use a newer version of the upload channel that won't
2742 // ignore the necessary headers for an empty Content-Type.
2743 nsCOMPtr<nsIUploadChannel2> uploadChannel2(
2744 do_QueryInterface(httpChannel));
2745 // This assertion will fire if buggy extensions are installed
2746 NS_ASSERTION(uploadChannel2, "http must support nsIUploadChannel2");
2747 if (uploadChannel2) {
2748 uploadChannel2->ExplicitSetUploadStream(
2749 uploadStream, aUploadContentType, mUploadTotal, mRequestMethod,
2750 false);
2751 } else {
2752 // The http channel doesn't support the new nsIUploadChannel2.
2753 // Emulate it as best we can using nsIUploadChannel.
2754 if (aUploadContentType.IsEmpty()) {
2755 aUploadContentType.AssignLiteral("application/octet-stream");
2757 nsCOMPtr<nsIUploadChannel> uploadChannel =
2758 do_QueryInterface(httpChannel);
2759 uploadChannel->SetUploadStream(uploadStream, aUploadContentType,
2760 mUploadTotal);
2761 // Reset the method to its original value
2762 rv = httpChannel->SetRequestMethod(mRequestMethod);
2763 MOZ_ASSERT(NS_SUCCEEDED(rv));
2768 // Should set a Content-Range header for blob scheme, and also slice the
2769 // blob appropriately, so we process the Range header here for later use.
2770 if (IsBlobURI(mRequestURL)) {
2771 nsAutoCString range;
2772 mAuthorRequestHeaders.Get("range", range);
2773 if (!range.IsVoid()) {
2774 rv = NS_SetChannelContentRangeForBlobURI(mChannel, mRequestURL, range);
2775 if (mFlagSynchronous && NS_FAILED(rv)) {
2776 // We later fire an error progress event for non-sync
2777 mState = XMLHttpRequest_Binding::DONE;
2778 return NS_ERROR_DOM_NETWORK_ERR;
2783 // Due to the chrome-only XHR.channel API, we need a hacky way to set the
2784 // SEC_COOKIES_INCLUDE *after* the channel has been has been created, since
2785 // .withCredentials can be called after open() is called.
2786 // Not doing this for privileged system XHRs since those don't use CORS.
2787 if (!IsSystemXHR() && !mIsAnon && mFlagACwithCredentials) {
2788 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2789 static_cast<net::LoadInfo*>(loadInfo.get())->SetIncludeCookiesSecFlag();
2792 // We never let XHR be blocked by head CSS/JS loads to avoid potential
2793 // deadlock where server generation of CSS/JS requires an XHR signal.
2794 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
2795 if (cos) {
2796 cos->AddClassFlags(nsIClassOfService::Unblocked);
2798 // Mark channel as urgent-start if the XHR is triggered by user input
2799 // events.
2800 if (UserActivation::IsHandlingUserInput()) {
2801 cos->AddClassFlags(nsIClassOfService::UrgentStart);
2805 // Disable Necko-internal response timeouts.
2806 nsCOMPtr<nsIHttpChannelInternal> internalHttpChannel(
2807 do_QueryInterface(mChannel));
2808 if (internalHttpChannel) {
2809 rv = internalHttpChannel->SetResponseTimeoutEnabled(false);
2810 MOZ_ASSERT(NS_SUCCEEDED(rv));
2813 if (!mIsAnon) {
2814 AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS);
2817 // Bypass the network cache in cases where it makes no sense:
2818 // POST responses are always unique, and we provide no API that would
2819 // allow our consumers to specify a "cache key" to access old POST
2820 // responses, so they are not worth caching.
2821 if (mRequestMethod.EqualsLiteral("POST")) {
2822 AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE |
2823 nsIRequest::INHIBIT_CACHING);
2824 } else {
2825 // When we are sync loading, we need to bypass the local cache when it would
2826 // otherwise block us waiting for exclusive access to the cache. If we
2827 // don't do this, then we could dead lock in some cases (see bug 309424).
2829 // Also don't block on the cache entry on async if it is busy - favoring
2830 // parallelism over cache hit rate for xhr. This does not disable the cache
2831 // everywhere - only in cases where more than one channel for the same URI
2832 // is accessed simultanously.
2833 AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
2836 EnsureChannelContentType();
2838 // Set up the preflight if needed
2839 if (!IsSystemXHR()) {
2840 nsTArray<nsCString> CORSUnsafeHeaders;
2841 mAuthorRequestHeaders.GetCORSUnsafeHeaders(CORSUnsafeHeaders);
2842 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2843 loadInfo->SetCorsPreflightInfo(CORSUnsafeHeaders,
2844 mFlagHadUploadListenersOnSend);
2847 // Hook us up to listen to redirects and the like. Only do this very late
2848 // since this creates a cycle between the channel and us. This cycle has
2849 // to be manually broken if anything below fails.
2850 mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
2851 mChannel->SetNotificationCallbacks(this);
2853 if (internalHttpChannel) {
2854 internalHttpChannel->SetBlockAuthPrompt(ShouldBlockAuthPrompt());
2857 // Because of bug 682305, we can't let listener be the XHR object itself
2858 // because JS wouldn't be able to use it. So create a listener around 'this'.
2859 // Make sure to hold a strong reference so that we don't leak the wrapper.
2860 nsCOMPtr<nsIStreamListener> listener = new net::nsStreamListenerWrapper(this);
2862 // Check if this XHR is created from a tracking script.
2863 // If yes, lower the channel's priority.
2864 if (StaticPrefs::privacy_trackingprotection_lower_network_priority()) {
2865 MaybeLowerChannelPriority();
2868 // Associate any originating stack with the channel.
2869 NotifyNetworkMonitorAlternateStack(mChannel, std::move(mOriginStack));
2871 // Start reading from the channel
2872 rv = mChannel->AsyncOpen(listener);
2873 listener = nullptr;
2874 if (NS_WARN_IF(NS_FAILED(rv))) {
2875 // Drop our ref to the channel to avoid cycles. Also drop channel's
2876 // ref to us to be extra safe.
2877 mChannel->SetNotificationCallbacks(mNotificationCallbacks);
2878 mChannel = nullptr;
2880 mErrorLoad = ErrorType::eChannelOpen;
2881 mErrorLoadDetail = rv;
2883 // Per spec, we throw on sync errors, but not async.
2884 if (mFlagSynchronous) {
2885 mState = XMLHttpRequest_Binding::DONE;
2886 return NS_ERROR_DOM_NETWORK_ERR;
2890 return NS_OK;
2893 already_AddRefed<PreloaderBase> XMLHttpRequestMainThread::FindPreload() {
2894 Document* doc = GetDocumentIfCurrent();
2895 if (!doc) {
2896 return nullptr;
2898 if (mPrincipal->IsSystemPrincipal() || IsSystemXHR()) {
2899 return nullptr;
2901 if (!mRequestMethod.EqualsLiteral("GET")) {
2902 // Preload can only do GET.
2903 return nullptr;
2905 if (!mAuthorRequestHeaders.IsEmpty()) {
2906 // Preload can't set headers.
2907 return nullptr;
2910 // mIsAnon overrules mFlagACwithCredentials.
2911 CORSMode cors = (mIsAnon || !mFlagACwithCredentials)
2912 ? CORSMode::CORS_ANONYMOUS
2913 : CORSMode::CORS_USE_CREDENTIALS;
2914 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2915 ReferrerInfo::CreateForFetch(mPrincipal, doc);
2916 auto key = PreloadHashKey::CreateAsFetch(mRequestURL, cors);
2917 RefPtr<PreloaderBase> preload = doc->Preloads().LookupPreload(key);
2918 if (!preload) {
2919 return nullptr;
2922 preload->RemoveSelf(doc);
2923 preload->NotifyUsage(doc, PreloaderBase::LoadBackground::Keep);
2925 return preload.forget();
2928 void XMLHttpRequestMainThread::EnsureChannelContentType() {
2929 MOZ_ASSERT(mChannel);
2931 // We don't mess with the content type of a blob URL.
2932 if (IsBlobURI(mRequestURL)) {
2933 return;
2936 // Since we expect XML data, set the type hint accordingly
2937 // if the channel doesn't know any content type.
2938 // This means that we always try to parse local files as XML
2939 // ignoring return value, as this is not critical. Use text/xml as fallback
2940 // MIME type.
2941 nsAutoCString contentType;
2942 if (NS_FAILED(mChannel->GetContentType(contentType)) ||
2943 contentType.IsEmpty() ||
2944 contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
2945 mChannel->SetContentType("text/xml"_ns);
2949 void XMLHttpRequestMainThread::ResumeTimeout() {
2950 DEBUG_WORKERREFS;
2951 MOZ_ASSERT(NS_IsMainThread());
2952 MOZ_ASSERT(mFlagSynchronous);
2954 if (mResumeTimeoutRunnable) {
2955 DispatchToMainThread(mResumeTimeoutRunnable.forget());
2956 mResumeTimeoutRunnable = nullptr;
2960 void XMLHttpRequestMainThread::Send(
2961 const Nullable<
2962 DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString>&
2963 aData,
2964 ErrorResult& aRv) {
2965 DEBUG_WORKERREFS1(mRequestURL);
2966 NOT_CALLABLE_IN_SYNC_SEND_RV
2968 if (!CanSend(aRv)) {
2969 return;
2972 if (aData.IsNull()) {
2973 SendInternal(nullptr, false, aRv);
2974 return;
2977 if (aData.Value().IsDocument()) {
2978 BodyExtractor<Document> body(&aData.Value().GetAsDocument());
2979 SendInternal(&body, true, aRv);
2980 return;
2983 if (aData.Value().IsBlob()) {
2984 BodyExtractor<const Blob> body(&aData.Value().GetAsBlob());
2985 SendInternal(&body, false, aRv);
2986 return;
2989 if (aData.Value().IsArrayBuffer()) {
2990 BodyExtractor<const ArrayBuffer> body(&aData.Value().GetAsArrayBuffer());
2991 SendInternal(&body, false, aRv);
2992 return;
2995 if (aData.Value().IsArrayBufferView()) {
2996 BodyExtractor<const ArrayBufferView> body(
2997 &aData.Value().GetAsArrayBufferView());
2998 SendInternal(&body, false, aRv);
2999 return;
3002 if (aData.Value().IsFormData()) {
3003 BodyExtractor<const FormData> body(&aData.Value().GetAsFormData());
3004 SendInternal(&body, false, aRv);
3005 return;
3008 if (aData.Value().IsURLSearchParams()) {
3009 BodyExtractor<const URLSearchParams> body(
3010 &aData.Value().GetAsURLSearchParams());
3011 SendInternal(&body, false, aRv);
3012 return;
3015 if (aData.Value().IsUSVString()) {
3016 BodyExtractor<const nsAString> body(&aData.Value().GetAsUSVString());
3017 SendInternal(&body, true, aRv);
3018 return;
3022 nsresult XMLHttpRequestMainThread::MaybeSilentSendFailure(nsresult aRv) {
3023 // Per spec, silently fail on async request failures; throw for sync.
3024 if (mFlagSynchronous) {
3025 mState = XMLHttpRequest_Binding::DONE;
3026 return NS_ERROR_DOM_NETWORK_ERR;
3029 // Defer the actual sending of async events just in case listeners
3030 // are attached after the send() method is called.
3031 Unused << NS_WARN_IF(
3032 NS_FAILED(DispatchToMainThread(NewRunnableMethod<ErrorProgressEventType>(
3033 "dom::XMLHttpRequestMainThread::CloseRequestWithError", this,
3034 &XMLHttpRequestMainThread::CloseRequestWithError, Events::error))));
3035 return NS_OK;
3038 bool XMLHttpRequestMainThread::CanSend(ErrorResult& aRv) {
3039 if (!mPrincipal) {
3040 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
3041 return false;
3044 // Step 1
3045 if (mState != XMLHttpRequest_Binding::OPENED) {
3046 aRv.ThrowInvalidStateError("XMLHttpRequest state must be OPENED.");
3047 return false;
3050 // Step 2
3051 if (mFlagSend) {
3052 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3053 return false;
3056 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
3057 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
3058 return false;
3061 return true;
3064 void XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody,
3065 bool aBodyIsDocumentOrString,
3066 ErrorResult& aRv) {
3067 DEBUG_WORKERREFS;
3068 MOZ_ASSERT(NS_IsMainThread());
3070 // We expect that CanSend has been called before we get here!
3071 // We cannot move the remaining two checks below there because
3072 // MaybeSilentSendFailure can cause unexpected side effects if called
3073 // too early.
3075 // If open() failed to create the channel, then throw a network error
3076 // as per spec. We really should create the channel here in send(), but
3077 // we have internal code relying on the channel being created in open().
3078 if (!mChannel) {
3079 mErrorLoad = ErrorType::eChannelOpen;
3080 mErrorLoadDetail = NS_ERROR_DOM_NETWORK_ERR;
3081 mFlagSend = true; // so CloseRequestWithError sets us to DONE.
3082 aRv = MaybeSilentSendFailure(mErrorLoadDetail);
3083 return;
3086 // non-GET requests aren't allowed for blob.
3087 if (IsBlobURI(mRequestURL) && !mRequestMethod.EqualsLiteral("GET")) {
3088 mErrorLoad = ErrorType::eChannelOpen;
3089 mErrorLoadDetail = NS_ERROR_DOM_NETWORK_ERR;
3090 mFlagSend = true; // so CloseRequestWithError sets us to DONE.
3091 aRv = MaybeSilentSendFailure(mErrorLoadDetail);
3092 return;
3095 // XXX We should probably send a warning to the JS console
3096 // if there are no event listeners set and we are doing
3097 // an asynchronous call.
3099 mUploadTransferred = 0;
3100 mUploadTotal = 0;
3101 // By default we don't have any upload, so mark upload complete.
3102 mUploadComplete = true;
3103 mErrorLoad = ErrorType::eOK;
3104 mErrorLoadDetail = NS_OK;
3105 mLoadTotal = -1;
3106 nsCOMPtr<nsIInputStream> uploadStream;
3107 nsAutoCString uploadContentType;
3108 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
3109 if (aBody && httpChannel && !mRequestMethod.EqualsLiteral("GET") &&
3110 !mRequestMethod.EqualsLiteral("HEAD")) {
3111 nsAutoCString charset;
3112 nsAutoCString defaultContentType;
3113 uint64_t size_u64;
3114 aRv = aBody->GetAsStream(getter_AddRefs(uploadStream), &size_u64,
3115 defaultContentType, charset);
3116 if (aRv.Failed()) {
3117 return;
3120 // make sure it fits within js MAX_SAFE_INTEGER
3121 mUploadTotal =
3122 net::InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1;
3124 if (uploadStream) {
3125 // If author set no Content-Type, use the default from GetAsStream().
3126 mAuthorRequestHeaders.Get("content-type", uploadContentType);
3127 if (uploadContentType.IsVoid()) {
3128 uploadContentType = defaultContentType;
3129 } else if (aBodyIsDocumentOrString) {
3130 UniquePtr<CMimeType> contentTypeRecord =
3131 CMimeType::Parse(uploadContentType);
3132 nsAutoCString charset;
3133 if (contentTypeRecord &&
3134 contentTypeRecord->GetParameterValue(kLiteralString_charset,
3135 charset) &&
3136 !charset.EqualsIgnoreCase("utf-8")) {
3137 contentTypeRecord->SetParameterValue(kLiteralString_charset,
3138 kLiteralString_UTF_8);
3139 contentTypeRecord->Serialize(uploadContentType);
3141 } else if (!charset.IsEmpty()) {
3142 // We don't want to set a charset for streams.
3143 // Replace all case-insensitive matches of the charset in the
3144 // content-type with the correct case.
3145 RequestHeaders::CharsetIterator iter(uploadContentType);
3146 while (iter.Next()) {
3147 if (!iter.Equals(charset, nsCaseInsensitiveCStringComparator)) {
3148 iter.Replace(charset);
3153 mUploadComplete = false;
3157 ResetResponse();
3159 // Check if we should enable cross-origin upload listeners.
3160 if (mUpload && mUpload->HasListeners()) {
3161 mFlagHadUploadListenersOnSend = true;
3164 mIsMappedArrayBuffer = false;
3165 if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
3166 StaticPrefs::dom_mapped_arraybuffer_enabled()) {
3167 nsCOMPtr<nsIURI> uri;
3168 nsAutoCString scheme;
3170 aRv = mChannel->GetURI(getter_AddRefs(uri));
3171 if (!aRv.Failed()) {
3172 uri->GetScheme(scheme);
3173 if (scheme.LowerCaseEqualsLiteral("jar")) {
3174 mIsMappedArrayBuffer = true;
3179 aRv = InitiateFetch(uploadStream.forget(), mUploadTotal, uploadContentType);
3180 if (aRv.Failed()) {
3181 return;
3184 // Start our timeout
3185 mRequestSentTime = PR_Now();
3186 StartTimeoutTimer();
3188 mWaitingForOnStopRequest = true;
3190 // Step 8
3191 mFlagSend = true;
3193 // If we're synchronous, spin an event loop here and wait
3194 RefPtr<Document> suspendedDoc;
3195 if (mFlagSynchronous) {
3196 auto scopeExit = MakeScopeExit([&] {
3197 CancelSyncTimeoutTimer();
3198 ResumeTimeout();
3199 ResumeEventDispatching();
3201 Maybe<AutoSuppressEventHandling> autoSuppress;
3203 mFlagSyncLooping = true;
3205 if (GetOwner()) {
3206 if (nsCOMPtr<nsPIDOMWindowOuter> topWindow =
3207 GetOwner()->GetOuterWindow()->GetInProcessTop()) {
3208 if (nsCOMPtr<nsPIDOMWindowInner> topInner =
3209 topWindow->GetCurrentInnerWindow()) {
3210 suspendedDoc = topWindow->GetExtantDoc();
3211 autoSuppress.emplace(topWindow->GetBrowsingContext());
3212 topInner->Suspend();
3213 mResumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
3218 SuspendEventDispatching();
3219 StopProgressEventTimer();
3221 SyncTimeoutType syncTimeoutType = MaybeStartSyncTimeoutTimer();
3222 if (syncTimeoutType == eErrorOrExpired) {
3223 Abort();
3224 aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
3225 return;
3228 nsAutoSyncOperation sync(suspendedDoc,
3229 SyncOperationBehavior::eSuspendInput);
3230 if (!SpinEventLoopUntil("XMLHttpRequestMainThread::SendInternal"_ns,
3231 [&]() { return !mFlagSyncLooping; })) {
3232 aRv.Throw(NS_ERROR_UNEXPECTED);
3233 return;
3236 // Time expired... We should throw.
3237 if (syncTimeoutType == eTimerStarted && !mSyncTimeoutTimer) {
3238 aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
3239 return;
3241 } else {
3242 // Now that we've successfully opened the channel, we can change state. Note
3243 // that this needs to come after the AsyncOpen() and rv check, because this
3244 // can run script that would try to restart this request, and that could end
3245 // up doing our AsyncOpen on a null channel if the reentered AsyncOpen
3246 // fails.
3247 StopProgressEventTimer();
3249 // Upload phase beginning; start the progress event timer if necessary.
3250 if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) {
3251 StartProgressEventTimer();
3253 // Dispatch loadstart events
3254 DispatchProgressEvent(this, Events::loadstart, 0, -1);
3255 if (mUpload && !mUploadComplete) {
3256 DispatchProgressEvent(mUpload, Events::loadstart, 0, mUploadTotal);
3260 if (!mChannel) {
3261 aRv = MaybeSilentSendFailure(NS_ERROR_DOM_NETWORK_ERR);
3265 // http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader
3266 void XMLHttpRequestMainThread::SetRequestHeader(const nsACString& aName,
3267 const nsACString& aValue,
3268 ErrorResult& aRv) {
3269 NOT_CALLABLE_IN_SYNC_SEND_RV
3271 // Step 1
3272 if (mState != XMLHttpRequest_Binding::OPENED) {
3273 aRv.ThrowInvalidStateError("XMLHttpRequest state must be OPENED.");
3274 return;
3277 // Step 2
3278 if (mFlagSend) {
3279 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3280 return;
3283 // Step 3
3284 nsAutoCString value;
3285 NS_TrimHTTPWhitespace(aValue, value);
3287 // Step 4
3288 if (!NS_IsValidHTTPToken(aName) || !NS_IsReasonableHTTPHeaderValue(value)) {
3289 aRv.Throw(NS_ERROR_DOM_INVALID_HEADER_NAME);
3290 return;
3293 // Step 5
3294 bool isPrivilegedCaller = IsSystemXHR();
3295 bool isForbiddenHeader =
3296 nsContentUtils::IsForbiddenRequestHeader(aName, aValue);
3297 if (!isPrivilegedCaller && isForbiddenHeader) {
3298 AutoTArray<nsString, 1> params;
3299 CopyUTF8toUTF16(aName, *params.AppendElement());
3300 LogMessage("ForbiddenHeaderWarning", GetOwner(), params);
3301 return;
3304 // Step 6.1
3305 // Skipping for now, as normalizing the case of header names may not be
3306 // web-compatible. See bug 1285036.
3308 // Step 6.2-6.3
3309 // Gecko-specific: invalid headers can be set by privileged
3310 // callers, but will not merge.
3311 if (isPrivilegedCaller && isForbiddenHeader) {
3312 mAuthorRequestHeaders.Set(aName, value);
3313 } else {
3314 mAuthorRequestHeaders.MergeOrSet(aName, value);
3318 void XMLHttpRequestMainThread::SetTimeout(uint32_t aTimeout, ErrorResult& aRv) {
3319 NOT_CALLABLE_IN_SYNC_SEND_RV
3321 if (mFlagSynchronous && mState != XMLHttpRequest_Binding::UNSENT &&
3322 HasOrHasHadOwner()) {
3323 /* Timeout is not supported for synchronous requests with an owning window,
3324 per XHR2 spec. */
3325 LogMessage("TimeoutSyncXHRWarning", GetOwner());
3326 aRv.ThrowInvalidAccessError(
3327 "synchronous XMLHttpRequests do not support timeout and responseType");
3328 return;
3331 mTimeoutMilliseconds = aTimeout;
3332 if (mRequestSentTime) {
3333 StartTimeoutTimer();
3337 nsIEventTarget* XMLHttpRequestMainThread::GetTimerEventTarget() {
3338 if (nsIGlobalObject* global = GetOwnerGlobal()) {
3339 return global->SerialEventTarget();
3341 return nullptr;
3344 nsresult XMLHttpRequestMainThread::DispatchToMainThread(
3345 already_AddRefed<nsIRunnable> aRunnable) {
3346 DEBUG_WORKERREFS;
3347 if (nsIGlobalObject* global = GetOwnerGlobal()) {
3348 return global->Dispatch(std::move(aRunnable));
3350 return NS_DispatchToMainThread(std::move(aRunnable));
3353 void XMLHttpRequestMainThread::StartTimeoutTimer() {
3354 DEBUG_WORKERREFS;
3355 MOZ_ASSERT(
3356 mRequestSentTime,
3357 "StartTimeoutTimer mustn't be called before the request was sent!");
3358 if (mState == XMLHttpRequest_Binding::DONE) {
3359 // do nothing!
3360 return;
3363 CancelTimeoutTimer();
3365 if (!mTimeoutMilliseconds) {
3366 return;
3369 if (!mTimeoutTimer) {
3370 mTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
3372 uint32_t elapsed =
3373 (uint32_t)((PR_Now() - mRequestSentTime) / PR_USEC_PER_MSEC);
3374 mTimeoutTimer->InitWithCallback(
3375 this, mTimeoutMilliseconds > elapsed ? mTimeoutMilliseconds - elapsed : 0,
3376 nsITimer::TYPE_ONE_SHOT);
3379 uint16_t XMLHttpRequestMainThread::ReadyState() const { return mState; }
3381 void XMLHttpRequestMainThread::OverrideMimeType(const nsAString& aMimeType,
3382 ErrorResult& aRv) {
3383 NOT_CALLABLE_IN_SYNC_SEND_RV
3385 if (mState == XMLHttpRequest_Binding::LOADING ||
3386 mState == XMLHttpRequest_Binding::DONE) {
3387 aRv.ThrowInvalidStateError(
3388 "Cannot call 'overrideMimeType()' on XMLHttpRequest after 'send()' "
3389 "(when its state is LOADING or DONE).");
3390 return;
3393 UniquePtr<MimeType> parsed = MimeType::Parse(aMimeType);
3394 if (parsed) {
3395 parsed->Serialize(mOverrideMimeType);
3396 } else {
3397 mOverrideMimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
3401 bool XMLHttpRequestMainThread::MozBackgroundRequest() const {
3402 return mFlagBackgroundRequest;
3405 void XMLHttpRequestMainThread::SetMozBackgroundRequestExternal(
3406 bool aMozBackgroundRequest, ErrorResult& aRv) {
3407 if (!IsSystemXHR()) {
3408 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
3409 return;
3412 if (mState != XMLHttpRequest_Binding::UNSENT) {
3413 // Can't change this while we're in the middle of something.
3414 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3415 return;
3418 mFlagBackgroundRequest = aMozBackgroundRequest;
3421 void XMLHttpRequestMainThread::SetMozBackgroundRequest(
3422 bool aMozBackgroundRequest, ErrorResult& aRv) {
3423 // No errors for this webIDL method on main-thread.
3424 SetMozBackgroundRequestExternal(aMozBackgroundRequest, IgnoreErrors());
3427 void XMLHttpRequestMainThread::SetOriginStack(
3428 UniquePtr<SerializedStackHolder> aOriginStack) {
3429 mOriginStack = std::move(aOriginStack);
3432 void XMLHttpRequestMainThread::SetSource(
3433 UniquePtr<ProfileChunkedBuffer> aSource) {
3434 if (!mChannel) {
3435 return;
3437 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
3439 if (httpChannel) {
3440 httpChannel->SetSource(std::move(aSource));
3444 bool XMLHttpRequestMainThread::WithCredentials() const {
3445 return mFlagACwithCredentials;
3448 void XMLHttpRequestMainThread::SetWithCredentials(bool aWithCredentials,
3449 ErrorResult& aRv) {
3450 NOT_CALLABLE_IN_SYNC_SEND_RV
3452 // Return error if we're already processing a request. Note that we can't use
3453 // ReadyState() here, because it can't differentiate between "opened" and
3454 // "sent", so we use mState directly.
3456 if ((mState != XMLHttpRequest_Binding::UNSENT &&
3457 mState != XMLHttpRequest_Binding::OPENED) ||
3458 mFlagSend || mIsAnon) {
3459 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3460 return;
3463 mFlagACwithCredentials = aWithCredentials;
3466 nsresult XMLHttpRequestMainThread::ChangeState(uint16_t aState,
3467 bool aBroadcast) {
3468 mState = aState;
3469 nsresult rv = NS_OK;
3471 if (aState != XMLHttpRequest_Binding::HEADERS_RECEIVED &&
3472 aState != XMLHttpRequest_Binding::LOADING) {
3473 StopProgressEventTimer();
3476 if (aBroadcast &&
3477 (!mFlagSynchronous || aState == XMLHttpRequest_Binding::OPENED ||
3478 aState == XMLHttpRequest_Binding::DONE)) {
3479 rv = FireReadystatechangeEvent();
3482 return rv;
3485 /////////////////////////////////////////////////////
3486 // nsIChannelEventSink methods:
3488 NS_IMETHODIMP
3489 XMLHttpRequestMainThread::AsyncOnChannelRedirect(
3490 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
3491 nsIAsyncVerifyRedirectCallback* callback) {
3492 DEBUG_WORKERREFS;
3493 MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
3495 // Prepare to receive callback
3496 mRedirectCallback = callback;
3497 mNewRedirectChannel = aNewChannel;
3499 if (mChannelEventSink) {
3500 nsCOMPtr<nsIAsyncVerifyRedirectCallback> fwd = EnsureXPCOMifier();
3502 nsresult rv = mChannelEventSink->AsyncOnChannelRedirect(
3503 aOldChannel, aNewChannel, aFlags, fwd);
3504 if (NS_FAILED(rv)) {
3505 mRedirectCallback = nullptr;
3506 mNewRedirectChannel = nullptr;
3508 return rv;
3511 // we need to strip Authentication headers for cross-origin requests
3512 // Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
3513 bool stripAuth =
3514 StaticPrefs::network_fetch_redirect_stripAuthHeader() &&
3515 NS_ShouldRemoveAuthHeaderOnRedirect(aOldChannel, aNewChannel, aFlags);
3517 OnRedirectVerifyCallback(NS_OK, stripAuth);
3519 return NS_OK;
3522 nsresult XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result,
3523 bool aStripAuth) {
3524 DEBUG_WORKERREFS;
3525 NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
3526 NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
3528 if (NS_SUCCEEDED(result)) {
3529 bool rewriteToGET = false;
3530 nsCOMPtr<nsIHttpChannel> oldHttpChannel = GetCurrentHttpChannel();
3531 // Fetch 4.4.11
3532 Unused << oldHttpChannel->ShouldStripRequestBodyHeader(mRequestMethod,
3533 &rewriteToGET);
3535 mChannel = mNewRedirectChannel;
3537 nsCOMPtr<nsIHttpChannel> newHttpChannel(do_QueryInterface(mChannel));
3538 if (newHttpChannel) {
3539 // Ensure all original headers are duplicated for the new channel (bug
3540 // #553888)
3541 mAuthorRequestHeaders.ApplyToChannel(newHttpChannel, rewriteToGET,
3542 aStripAuth);
3544 } else {
3545 mErrorLoad = ErrorType::eRedirect;
3546 mErrorLoadDetail = result;
3549 mNewRedirectChannel = nullptr;
3551 mRedirectCallback->OnRedirectVerifyCallback(result);
3552 mRedirectCallback = nullptr;
3554 // It's important that we return success here. If we return the result code
3555 // that we were passed, JavaScript callers who cancel the redirect will wind
3556 // up throwing an exception in the process.
3557 return NS_OK;
3560 /////////////////////////////////////////////////////
3561 // nsIProgressEventSink methods:
3564 NS_IMETHODIMP
3565 XMLHttpRequestMainThread::OnProgress(nsIRequest* aRequest, int64_t aProgress,
3566 int64_t aProgressMax) {
3567 DEBUG_WORKERREFS;
3568 // When uploading, OnProgress reports also headers in aProgress and
3569 // aProgressMax. So, try to remove the headers, if possible.
3570 bool lengthComputable = (aProgressMax != -1);
3571 if (InUploadPhase()) {
3572 int64_t loaded = aProgress;
3573 if (lengthComputable) {
3574 int64_t headerSize = aProgressMax - mUploadTotal;
3575 loaded -= headerSize;
3577 mUploadTransferred = loaded;
3578 mProgressSinceLastProgressEvent = true;
3580 if (!mFlagSynchronous && !mProgressTimerIsActive) {
3581 StartProgressEventTimer();
3583 } else {
3584 mLoadTotal = aProgressMax;
3585 mLoadTransferred = aProgress;
3586 // OnDataAvailable() handles mProgressSinceLastProgressEvent
3587 // for the download phase.
3590 if (mProgressEventSink) {
3591 mProgressEventSink->OnProgress(aRequest, aProgress, aProgressMax);
3594 return NS_OK;
3597 NS_IMETHODIMP
3598 XMLHttpRequestMainThread::OnStatus(nsIRequest* aRequest, nsresult aStatus,
3599 const char16_t* aStatusArg) {
3600 DEBUG_WORKERREFS;
3601 if (mProgressEventSink) {
3602 mProgressEventSink->OnStatus(aRequest, aStatus, aStatusArg);
3605 return NS_OK;
3608 bool XMLHttpRequestMainThread::AllowUploadProgress() {
3609 return !IsCrossSiteCORSRequest() || mFlagHadUploadListenersOnSend;
3612 /////////////////////////////////////////////////////
3613 // nsIInterfaceRequestor methods:
3615 NS_IMETHODIMP
3616 XMLHttpRequestMainThread::GetInterface(const nsIID& aIID, void** aResult) {
3617 nsresult rv;
3619 // Make sure to return ourselves for the channel event sink interface and
3620 // progress event sink interface, no matter what. We can forward these to
3621 // mNotificationCallbacks if it wants to get notifications for them. But we
3622 // need to see these notifications for proper functioning.
3623 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
3624 mChannelEventSink = do_GetInterface(mNotificationCallbacks);
3625 *aResult = static_cast<nsIChannelEventSink*>(EnsureXPCOMifier().take());
3626 return NS_OK;
3627 } else if (aIID.Equals(NS_GET_IID(nsIProgressEventSink))) {
3628 mProgressEventSink = do_GetInterface(mNotificationCallbacks);
3629 *aResult = static_cast<nsIProgressEventSink*>(EnsureXPCOMifier().take());
3630 return NS_OK;
3633 // Now give mNotificationCallbacks (if non-null) a chance to return the
3634 // desired interface.
3635 if (mNotificationCallbacks) {
3636 rv = mNotificationCallbacks->GetInterface(aIID, aResult);
3637 if (NS_SUCCEEDED(rv)) {
3638 NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!");
3639 return rv;
3643 if (!mFlagBackgroundRequest && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
3644 aIID.Equals(NS_GET_IID(nsIAuthPrompt2)))) {
3645 nsCOMPtr<nsIPromptFactory> wwatch =
3646 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
3647 NS_ENSURE_SUCCESS(rv, rv);
3649 // Get the an auth prompter for our window so that the parenting
3650 // of the dialogs works as it should when using tabs.
3651 nsCOMPtr<nsPIDOMWindowOuter> window;
3652 if (GetOwner()) {
3653 window = GetOwner()->GetOuterWindow();
3655 return wwatch->GetPrompt(window, aIID, reinterpret_cast<void**>(aResult));
3658 // Now check for the various XHR non-DOM interfaces, except
3659 // nsIProgressEventSink and nsIChannelEventSink which we already
3660 // handled above.
3661 if (aIID.Equals(NS_GET_IID(nsIStreamListener))) {
3662 *aResult = static_cast<nsIStreamListener*>(EnsureXPCOMifier().take());
3663 return NS_OK;
3665 if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) {
3666 *aResult = static_cast<nsIRequestObserver*>(EnsureXPCOMifier().take());
3667 return NS_OK;
3669 if (aIID.Equals(NS_GET_IID(nsITimerCallback))) {
3670 *aResult = static_cast<nsITimerCallback*>(EnsureXPCOMifier().take());
3671 return NS_OK;
3674 return QueryInterface(aIID, aResult);
3677 void XMLHttpRequestMainThread::GetInterface(
3678 JSContext* aCx, JS::Handle<JS::Value> aIID,
3679 JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
3680 dom::GetInterface(aCx, this, aIID, aRetval, aRv);
3683 XMLHttpRequestUpload* XMLHttpRequestMainThread::GetUpload(ErrorResult& aRv) {
3684 if (!mUpload) {
3685 mUpload = new XMLHttpRequestUpload(this);
3687 return mUpload;
3690 bool XMLHttpRequestMainThread::MozAnon() const { return mIsAnon; }
3692 bool XMLHttpRequestMainThread::MozSystem() const { return IsSystemXHR(); }
3694 void XMLHttpRequestMainThread::HandleTimeoutCallback() {
3695 DEBUG_WORKERREFS;
3696 if (mState == XMLHttpRequest_Binding::DONE) {
3697 MOZ_ASSERT_UNREACHABLE(
3698 "XMLHttpRequestMainThread::HandleTimeoutCallback "
3699 "with completed request");
3700 // do nothing!
3701 return;
3704 mFlagTimedOut = true;
3705 CloseRequestWithError(Events::timeout);
3708 void XMLHttpRequestMainThread::CancelTimeoutTimer() {
3709 DEBUG_WORKERREFS;
3710 if (mTimeoutTimer) {
3711 mTimeoutTimer->Cancel();
3712 mTimeoutTimer = nullptr;
3716 NS_IMETHODIMP
3717 XMLHttpRequestMainThread::Notify(nsITimer* aTimer) {
3718 DEBUG_WORKERREFS;
3719 if (mProgressNotifier == aTimer) {
3720 HandleProgressTimerCallback();
3721 return NS_OK;
3724 if (mTimeoutTimer == aTimer) {
3725 HandleTimeoutCallback();
3726 return NS_OK;
3729 if (mSyncTimeoutTimer == aTimer) {
3730 HandleSyncTimeoutTimer();
3731 return NS_OK;
3734 // Just in case some JS user wants to QI to nsITimerCallback and play with
3735 // us...
3736 NS_WARNING("Unexpected timer!");
3737 return NS_ERROR_INVALID_POINTER;
3740 void XMLHttpRequestMainThread::HandleProgressTimerCallback() {
3741 DEBUG_WORKERREFS;
3742 // Don't fire the progress event if mLoadTotal is 0, see XHR spec step 6.1
3743 if (!mLoadTotal && mLoadTransferred) {
3744 return;
3747 mProgressTimerIsActive = false;
3749 if (!mProgressSinceLastProgressEvent || mErrorLoad != ErrorType::eOK) {
3750 return;
3753 if (InUploadPhase()) {
3754 if (mUpload && !mUploadComplete && mFlagHadUploadListenersOnSend) {
3755 DispatchProgressEvent(mUpload, Events::progress, mUploadTransferred,
3756 mUploadTotal);
3758 } else {
3759 FireReadystatechangeEvent();
3760 DispatchProgressEvent(this, Events::progress, mLoadTransferred, mLoadTotal);
3763 mProgressSinceLastProgressEvent = false;
3765 StartProgressEventTimer();
3768 void XMLHttpRequestMainThread::StopProgressEventTimer() {
3769 if (mProgressNotifier) {
3770 mProgressTimerIsActive = false;
3771 mProgressNotifier->Cancel();
3775 void XMLHttpRequestMainThread::StartProgressEventTimer() {
3776 if (!mProgressNotifier) {
3777 mProgressNotifier = NS_NewTimer(GetTimerEventTarget());
3779 if (mProgressNotifier) {
3780 mProgressTimerIsActive = true;
3781 mProgressNotifier->Cancel();
3782 mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
3783 nsITimer::TYPE_ONE_SHOT);
3787 XMLHttpRequestMainThread::SyncTimeoutType
3788 XMLHttpRequestMainThread::MaybeStartSyncTimeoutTimer() {
3789 MOZ_ASSERT(mFlagSynchronous);
3791 Document* doc = GetDocumentIfCurrent();
3792 if (!doc || !doc->GetPageUnloadingEventTimeStamp()) {
3793 return eNoTimerNeeded;
3796 // If we are in a beforeunload or a unload event, we must force a timeout.
3797 TimeDuration diff =
3798 (TimeStamp::NowLoRes() - doc->GetPageUnloadingEventTimeStamp());
3799 if (diff.ToMilliseconds() > MAX_SYNC_TIMEOUT_WHEN_UNLOADING) {
3800 return eErrorOrExpired;
3803 mSyncTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
3804 if (!mSyncTimeoutTimer) {
3805 return eErrorOrExpired;
3808 uint32_t timeout = MAX_SYNC_TIMEOUT_WHEN_UNLOADING - diff.ToMilliseconds();
3809 nsresult rv = mSyncTimeoutTimer->InitWithCallback(this, timeout,
3810 nsITimer::TYPE_ONE_SHOT);
3811 return NS_FAILED(rv) ? eErrorOrExpired : eTimerStarted;
3814 void XMLHttpRequestMainThread::HandleSyncTimeoutTimer() {
3815 MOZ_ASSERT(mSyncTimeoutTimer);
3816 MOZ_ASSERT(mFlagSyncLooping);
3818 CancelSyncTimeoutTimer();
3819 Abort();
3820 mErrorLoadDetail = NS_ERROR_DOM_TIMEOUT_ERR;
3823 void XMLHttpRequestMainThread::CancelSyncTimeoutTimer() {
3824 if (mSyncTimeoutTimer) {
3825 mSyncTimeoutTimer->Cancel();
3826 mSyncTimeoutTimer = nullptr;
3830 already_AddRefed<nsXMLHttpRequestXPCOMifier>
3831 XMLHttpRequestMainThread::EnsureXPCOMifier() {
3832 if (!mXPCOMifier) {
3833 mXPCOMifier = new nsXMLHttpRequestXPCOMifier(this);
3835 RefPtr<nsXMLHttpRequestXPCOMifier> newRef(mXPCOMifier);
3836 return newRef.forget();
3839 bool XMLHttpRequestMainThread::ShouldBlockAuthPrompt() {
3840 // Verify that it's ok to prompt for credentials here, per spec
3841 // http://xhr.spec.whatwg.org/#the-send%28%29-method
3843 if (mAuthorRequestHeaders.Has("authorization")) {
3844 return true;
3847 nsCOMPtr<nsIURI> uri;
3848 nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
3849 if (NS_WARN_IF(NS_FAILED(rv))) {
3850 return false;
3853 // Also skip if a username and/or password is provided in the URI.
3854 bool hasUserPass;
3855 return NS_SUCCEEDED(uri->GetHasUserPass(&hasUserPass)) && hasUserPass;
3858 void XMLHttpRequestMainThread::TruncateResponseText() {
3859 mResponseText.Truncate();
3860 XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
3863 NS_IMPL_ISUPPORTS(XMLHttpRequestMainThread::nsHeaderVisitor,
3864 nsIHttpHeaderVisitor)
3866 NS_IMETHODIMP XMLHttpRequestMainThread::nsHeaderVisitor::VisitHeader(
3867 const nsACString& header, const nsACString& value) {
3868 if (mXHR.IsSafeHeader(header, mHttpChannel)) {
3869 nsAutoCString lowerHeader(header);
3870 ToLowerCase(lowerHeader);
3871 if (!mHeaderList.InsertElementSorted(HeaderEntry(lowerHeader, value),
3872 fallible)) {
3873 return NS_ERROR_OUT_OF_MEMORY;
3876 return NS_OK;
3879 XMLHttpRequestMainThread::nsHeaderVisitor::nsHeaderVisitor(
3880 const XMLHttpRequestMainThread& aXMLHttpRequest,
3881 NotNull<nsIHttpChannel*> aHttpChannel)
3882 : mXHR(aXMLHttpRequest), mHttpChannel(aHttpChannel) {}
3884 XMLHttpRequestMainThread::nsHeaderVisitor::~nsHeaderVisitor() = default;
3886 void XMLHttpRequestMainThread::MaybeCreateBlobStorage() {
3887 DEBUG_WORKERREFS;
3888 MOZ_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
3890 if (mBlobStorage) {
3891 return;
3894 MutableBlobStorage::MutableBlobStorageType storageType =
3895 BasePrincipal::Cast(mPrincipal)->PrivateBrowsingId() == 0
3896 ? MutableBlobStorage::eCouldBeInTemporaryFile
3897 : MutableBlobStorage::eOnlyInMemory;
3899 nsCOMPtr<nsIEventTarget> eventTarget;
3900 if (nsIGlobalObject* global = GetOwnerGlobal()) {
3901 eventTarget = global->SerialEventTarget();
3904 mBlobStorage = new MutableBlobStorage(storageType, eventTarget);
3907 void XMLHttpRequestMainThread::BlobStoreCompleted(
3908 MutableBlobStorage* aBlobStorage, BlobImpl* aBlobImpl, nsresult aRv) {
3909 DEBUG_WORKERREFS;
3910 // Ok, the state is changed...
3911 if (mBlobStorage != aBlobStorage || NS_FAILED(aRv)) {
3912 return;
3915 MOZ_ASSERT(mState != XMLHttpRequest_Binding::DONE);
3917 mResponseBlobImpl = aBlobImpl;
3918 mBlobStorage = nullptr;
3920 ChangeStateToDone(mFlagSyncLooping);
3923 NS_IMETHODIMP
3924 XMLHttpRequestMainThread::GetName(nsACString& aName) {
3925 aName.AssignLiteral("XMLHttpRequest");
3926 return NS_OK;
3929 // nsXMLHttpRequestXPCOMifier implementation
3930 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpRequestXPCOMifier)
3931 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
3932 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
3933 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
3934 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
3935 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
3936 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
3937 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
3938 NS_INTERFACE_MAP_ENTRY(nsINamed)
3939 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
3940 NS_INTERFACE_MAP_END
3942 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpRequestXPCOMifier)
3943 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpRequestXPCOMifier)
3945 // Can't NS_IMPL_CYCLE_COLLECTION( because mXHR has ambiguous
3946 // inheritance from nsISupports.
3947 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequestXPCOMifier)
3949 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpRequestXPCOMifier)
3950 if (tmp->mXHR) {
3951 tmp->mXHR->mXPCOMifier = nullptr;
3953 NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR)
3954 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
3956 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpRequestXPCOMifier)
3957 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXHR)
3958 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
3960 NS_IMETHODIMP
3961 nsXMLHttpRequestXPCOMifier::GetInterface(const nsIID& aIID, void** aResult) {
3962 // Return ourselves for the things we implement (except
3963 // nsIInterfaceRequestor) and the XHR for the rest.
3964 if (!aIID.Equals(NS_GET_IID(nsIInterfaceRequestor))) {
3965 nsresult rv = QueryInterface(aIID, aResult);
3966 if (NS_SUCCEEDED(rv)) {
3967 return rv;
3971 return mXHR->GetInterface(aIID, aResult);
3974 ArrayBufferBuilder::ArrayBufferBuilder()
3975 : mMutex("ArrayBufferBuilder"),
3976 mDataPtr(nullptr),
3977 mCapacity(0),
3978 mLength(0),
3979 mMapPtr(nullptr),
3980 mNeutered(false) {}
3982 ArrayBufferBuilder::~ArrayBufferBuilder() {
3983 if (mDataPtr) {
3984 JS_free(nullptr, mDataPtr);
3987 if (mMapPtr) {
3988 JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
3989 mMapPtr = nullptr;
3992 mDataPtr = nullptr;
3993 mCapacity = mLength = 0;
3996 bool ArrayBufferBuilder::SetCapacity(uint32_t aNewCap) {
3997 MutexAutoLock lock(mMutex);
3998 return SetCapacityInternal(aNewCap, lock);
4001 bool ArrayBufferBuilder::SetCapacityInternal(
4002 uint32_t aNewCap, const MutexAutoLock& aProofOfLock) {
4003 MOZ_ASSERT(!mMapPtr);
4004 MOZ_ASSERT(!mNeutered);
4006 // To ensure that realloc won't free mDataPtr, use a size of 1
4007 // instead of 0.
4008 uint8_t* newdata = (uint8_t*)js_realloc(mDataPtr, aNewCap ? aNewCap : 1);
4010 if (!newdata) {
4011 return false;
4014 if (aNewCap > mCapacity) {
4015 memset(newdata + mCapacity, 0, aNewCap - mCapacity);
4018 mDataPtr = newdata;
4019 mCapacity = aNewCap;
4020 if (mLength > aNewCap) {
4021 mLength = aNewCap;
4024 return true;
4027 bool ArrayBufferBuilder::Append(const uint8_t* aNewData, uint32_t aDataLen,
4028 uint32_t aMaxGrowth) {
4029 MutexAutoLock lock(mMutex);
4030 MOZ_ASSERT(!mMapPtr);
4031 MOZ_ASSERT(!mNeutered);
4033 CheckedUint32 neededCapacity = mLength;
4034 neededCapacity += aDataLen;
4035 if (!neededCapacity.isValid()) {
4036 return false;
4038 if (mLength + aDataLen > mCapacity) {
4039 CheckedUint32 newcap = mCapacity;
4040 // Double while under aMaxGrowth or if not specified.
4041 if (!aMaxGrowth || mCapacity < aMaxGrowth) {
4042 newcap *= 2;
4043 } else {
4044 newcap += aMaxGrowth;
4047 if (!newcap.isValid()) {
4048 return false;
4051 // But make sure there's always enough to satisfy our request.
4052 if (newcap.value() < neededCapacity.value()) {
4053 newcap = neededCapacity;
4056 if (!SetCapacityInternal(newcap.value(), lock)) {
4057 return false;
4061 // Assert that the region isn't overlapping so we can memcpy.
4062 MOZ_ASSERT(
4063 !AreOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength, aDataLen));
4065 memcpy(mDataPtr + mLength, aNewData, aDataLen);
4066 mLength += aDataLen;
4068 return true;
4071 uint32_t ArrayBufferBuilder::Length() {
4072 MutexAutoLock lock(mMutex);
4073 MOZ_ASSERT(!mNeutered);
4074 return mLength;
4077 uint32_t ArrayBufferBuilder::Capacity() {
4078 MutexAutoLock lock(mMutex);
4079 MOZ_ASSERT(!mNeutered);
4080 return mCapacity;
4083 JSObject* ArrayBufferBuilder::TakeArrayBuffer(JSContext* aCx) {
4084 MutexAutoLock lock(mMutex);
4085 MOZ_DIAGNOSTIC_ASSERT(!mNeutered);
4087 if (mMapPtr) {
4088 JSObject* obj = JS::NewMappedArrayBufferWithContents(aCx, mLength, mMapPtr);
4089 if (!obj) {
4090 JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
4093 mMapPtr = nullptr;
4094 mNeutered = true;
4096 // The memory-mapped contents will be released when the ArrayBuffer becomes
4097 // detached or is GC'd.
4098 return obj;
4101 // we need to check for mLength == 0, because nothing may have been
4102 // added
4103 if (mCapacity > mLength || mLength == 0) {
4104 if (!SetCapacityInternal(mLength, lock)) {
4105 return nullptr;
4109 // |mDataPtr| will be deallocated in ArrayBufferBuilder's destructor when this
4110 // ArrayBuffer allocation failed.
4111 JSObject* obj = JS::NewArrayBufferWithContents(
4112 aCx, mLength, mDataPtr,
4113 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory);
4114 if (!obj) {
4115 return nullptr;
4118 mDataPtr = nullptr;
4119 mCapacity = mLength = 0;
4121 mNeutered = true;
4122 return obj;
4125 nsresult ArrayBufferBuilder::MapToFileInPackage(const nsCString& aFile,
4126 nsIFile* aJarFile) {
4127 MutexAutoLock lock(mMutex);
4128 MOZ_ASSERT(NS_IsMainThread());
4129 MOZ_ASSERT(!mNeutered);
4131 nsresult rv;
4133 // Open Jar file to get related attributes of target file.
4134 RefPtr<nsZipArchive> zip = nsZipArchive::OpenArchive(aJarFile);
4135 if (!zip) {
4136 return NS_ERROR_FAILURE;
4138 nsZipItem* zipItem = zip->GetItem(aFile.get());
4139 if (!zipItem) {
4140 return NS_ERROR_FILE_NOT_FOUND;
4143 // If file was added to the package as stored(uncompressed), map to the
4144 // offset of file in zip package.
4145 if (!zipItem->Compression()) {
4146 uint32_t offset = zip->GetDataOffset(zipItem);
4147 uint32_t size = zipItem->RealSize();
4148 mozilla::AutoFDClose pr_fd;
4149 rv = aJarFile->OpenNSPRFileDesc(PR_RDONLY, 0, getter_Transfers(pr_fd));
4150 if (NS_FAILED(rv)) {
4151 return rv;
4153 mMapPtr = JS::CreateMappedArrayBufferContents(
4154 PR_FileDesc2NativeHandle(pr_fd.get()), offset, size);
4155 if (mMapPtr) {
4156 mLength = size;
4157 return NS_OK;
4160 return NS_ERROR_FAILURE;
4163 /* static */
4164 bool ArrayBufferBuilder::AreOverlappingRegions(const uint8_t* aStart1,
4165 uint32_t aLength1,
4166 const uint8_t* aStart2,
4167 uint32_t aLength2) {
4168 const uint8_t* end1 = aStart1 + aLength1;
4169 const uint8_t* end2 = aStart2 + aLength2;
4171 const uint8_t* max_start = aStart1 > aStart2 ? aStart1 : aStart2;
4172 const uint8_t* min_end = end1 < end2 ? end1 : end2;
4174 return max_start < min_end;
4177 RequestHeaders::RequestHeader* RequestHeaders::Find(const nsACString& aName) {
4178 for (RequestHeaders::RequestHeader& header : mHeaders) {
4179 if (header.mName.Equals(aName, nsCaseInsensitiveCStringComparator)) {
4180 return &header;
4183 return nullptr;
4186 bool RequestHeaders::IsEmpty() const { return mHeaders.IsEmpty(); }
4188 bool RequestHeaders::Has(const char* aName) {
4189 return Has(nsDependentCString(aName));
4192 bool RequestHeaders::Has(const nsACString& aName) { return !!Find(aName); }
4194 void RequestHeaders::Get(const char* aName, nsACString& aValue) {
4195 Get(nsDependentCString(aName), aValue);
4198 void RequestHeaders::Get(const nsACString& aName, nsACString& aValue) {
4199 RequestHeader* header = Find(aName);
4200 if (header) {
4201 aValue = header->mValue;
4202 } else {
4203 aValue.SetIsVoid(true);
4207 void RequestHeaders::Set(const char* aName, const nsACString& aValue) {
4208 Set(nsDependentCString(aName), aValue);
4211 void RequestHeaders::Set(const nsACString& aName, const nsACString& aValue) {
4212 RequestHeader* header = Find(aName);
4213 if (header) {
4214 header->mValue.Assign(aValue);
4215 } else {
4216 RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
4217 mHeaders.AppendElement(newHeader);
4221 void RequestHeaders::MergeOrSet(const char* aName, const nsACString& aValue) {
4222 MergeOrSet(nsDependentCString(aName), aValue);
4225 void RequestHeaders::MergeOrSet(const nsACString& aName,
4226 const nsACString& aValue) {
4227 RequestHeader* header = Find(aName);
4228 if (header) {
4229 header->mValue.AppendLiteral(", ");
4230 header->mValue.Append(aValue);
4231 } else {
4232 RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
4233 mHeaders.AppendElement(newHeader);
4237 void RequestHeaders::Clear() { mHeaders.Clear(); }
4239 void RequestHeaders::ApplyToChannel(nsIHttpChannel* aChannel,
4240 bool aStripRequestBodyHeader,
4241 bool aStripAuthHeader) const {
4242 for (const RequestHeader& header : mHeaders) {
4243 if (aStripRequestBodyHeader &&
4244 (header.mName.LowerCaseEqualsASCII("content-type") ||
4245 header.mName.LowerCaseEqualsASCII("content-encoding") ||
4246 header.mName.LowerCaseEqualsASCII("content-language") ||
4247 header.mName.LowerCaseEqualsASCII("content-location"))) {
4248 continue;
4251 if (aStripAuthHeader &&
4252 header.mName.LowerCaseEqualsASCII("authorization")) {
4253 continue;
4256 // Update referrerInfo to override referrer header in system privileged.
4257 if (header.mName.LowerCaseEqualsASCII("referer")) {
4258 DebugOnly<nsresult> rv = aChannel->SetNewReferrerInfo(
4259 header.mValue, nsIReferrerInfo::ReferrerPolicyIDL::UNSAFE_URL, true);
4260 MOZ_ASSERT(NS_SUCCEEDED(rv));
4262 if (header.mValue.IsEmpty()) {
4263 DebugOnly<nsresult> rv = aChannel->SetEmptyRequestHeader(header.mName);
4264 MOZ_ASSERT(NS_SUCCEEDED(rv));
4265 } else {
4266 DebugOnly<nsresult> rv =
4267 aChannel->SetRequestHeader(header.mName, header.mValue, false);
4268 MOZ_ASSERT(NS_SUCCEEDED(rv));
4273 void RequestHeaders::GetCORSUnsafeHeaders(nsTArray<nsCString>& aArray) const {
4274 for (const RequestHeader& header : mHeaders) {
4275 if (!nsContentUtils::IsCORSSafelistedRequestHeader(header.mName,
4276 header.mValue)) {
4277 aArray.AppendElement(header.mName);
4282 RequestHeaders::CharsetIterator::CharsetIterator(nsACString& aSource)
4283 : mValid(false),
4284 mCurPos(-1),
4285 mCurLen(-1),
4286 mCutoff(aSource.Length()),
4287 mSource(aSource) {}
4289 bool RequestHeaders::CharsetIterator::Equals(
4290 const nsACString& aOther, const nsCStringComparator& aCmp) const {
4291 if (mValid) {
4292 return Substring(mSource, mCurPos, mCurLen).Equals(aOther, aCmp);
4293 } else {
4294 return false;
4298 void RequestHeaders::CharsetIterator::Replace(const nsACString& aReplacement) {
4299 if (mValid) {
4300 mSource.Replace(mCurPos, mCurLen, aReplacement);
4301 mCurLen = aReplacement.Length();
4305 bool RequestHeaders::CharsetIterator::Next() {
4306 int32_t start, end;
4307 nsAutoCString charset;
4309 // Look for another charset declaration in the string, limiting the
4310 // search to only the characters before the parts we've already searched
4311 // (before mCutoff), so that we don't find the same charset twice.
4312 NS_ExtractCharsetFromContentType(Substring(mSource, 0, mCutoff), charset,
4313 &mValid, &start, &end);
4315 if (!mValid) {
4316 return false;
4319 // Everything after the = sign is the part of the charset we want.
4320 mCurPos = mSource.FindChar('=', start) + 1;
4321 mCurLen = end - mCurPos;
4323 // Special case: the extracted charset is quoted with single quotes.
4324 // For the purpose of preserving what was set we want to handle them
4325 // as delimiters (although they aren't really).
4326 if (charset.Length() >= 2 && charset.First() == '\'' &&
4327 charset.Last() == '\'') {
4328 ++mCurPos;
4329 mCurLen -= 2;
4332 mCutoff = start;
4334 return true;
4337 } // namespace mozilla::dom