Bug 1707290 [wpt PR 28671] - Auto-expand details elements for find-in-page, a=testonly
[gecko.git] / dom / xhr / XMLHttpRequestMainThread.cpp
blob7171b8fccce9a51a72ff13a7c14933ebd465e5c6
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/BlobBinding.h"
18 #include "mozilla/dom/BlobURLProtocolHandler.h"
19 #include "mozilla/dom/DocGroup.h"
20 #include "mozilla/dom/DOMString.h"
21 #include "mozilla/dom/File.h"
22 #include "mozilla/dom/FileBinding.h"
23 #include "mozilla/dom/FileCreatorHelper.h"
24 #include "mozilla/dom/FetchUtil.h"
25 #include "mozilla/dom/FormData.h"
26 #include "mozilla/dom/MutableBlobStorage.h"
27 #include "mozilla/dom/XMLDocument.h"
28 #include "mozilla/dom/URLSearchParams.h"
29 #include "mozilla/dom/UserActivation.h"
30 #include "mozilla/dom/Promise.h"
31 #include "mozilla/dom/PromiseNativeHandler.h"
32 #include "mozilla/dom/WorkerError.h"
33 #include "mozilla/Encoding.h"
34 #include "mozilla/EventDispatcher.h"
35 #include "mozilla/EventListenerManager.h"
36 #include "mozilla/HoldDropJSObjects.h"
37 #include "mozilla/LoadInfo.h"
38 #include "mozilla/LoadContext.h"
39 #include "mozilla/MemoryReporting.h"
40 #include "mozilla/PreloaderBase.h"
41 #include "mozilla/ScopeExit.h"
42 #include "mozilla/SpinEventLoopUntil.h"
43 #include "mozilla/StaticPrefs_dom.h"
44 #include "mozilla/StaticPrefs_network.h"
45 #include "mozilla/StaticPrefs_privacy.h"
46 #include "mozilla/dom/ProgressEvent.h"
47 #include "nsIJARChannel.h"
48 #include "nsIJARURI.h"
49 #include "nsLayoutCID.h"
50 #include "nsReadableUtils.h"
51 #include "nsSandboxFlags.h"
53 #include "nsIURI.h"
54 #include "nsIURIMutator.h"
55 #include "nsILoadGroup.h"
56 #include "nsNetUtil.h"
57 #include "nsStringStream.h"
58 #include "nsIAuthPrompt.h"
59 #include "nsIAuthPrompt2.h"
60 #include "nsIClassOfService.h"
61 #include "nsIHttpChannel.h"
62 #include "nsISupportsPriority.h"
63 #include "nsIInterfaceRequestorUtils.h"
64 #include "nsStreamUtils.h"
65 #include "nsThreadUtils.h"
66 #include "nsIUploadChannel.h"
67 #include "nsIUploadChannel2.h"
68 #include "nsXPCOM.h"
69 #include "nsIDOMEventListener.h"
70 #include "nsVariant.h"
71 #include "nsIScriptError.h"
72 #include "nsICachingChannel.h"
73 #include "nsICookieJarSettings.h"
74 #include "nsContentUtils.h"
75 #include "nsCycleCollectionParticipant.h"
76 #include "nsError.h"
77 #include "nsIPromptFactory.h"
78 #include "nsIWindowWatcher.h"
79 #include "nsIConsoleService.h"
80 #include "nsAsyncRedirectVerifyHelper.h"
81 #include "nsStringBuffer.h"
82 #include "nsIFileChannel.h"
83 #include "mozilla/Telemetry.h"
84 #include "js/ArrayBuffer.h" // JS::{Create,Release}MappedArrayBufferContents,New{,Mapped}ArrayBufferWithContents
85 #include "js/JSON.h" // JS_ParseJSON
86 #include "js/MemoryFunctions.h"
87 #include "js/RootingAPI.h" // JS::{{,Mutable}Handle,Rooted}
88 #include "js/Value.h" // JS::{,Undefined}Value
89 #include "jsapi.h" // JS_ClearPendingException
90 #include "GeckoProfiler.h"
91 #include "mozilla/dom/XMLHttpRequestBinding.h"
92 #include "mozilla/Attributes.h"
93 #include "MultipartBlobImpl.h"
94 #include "nsIPermissionManager.h"
95 #include "nsMimeTypes.h"
96 #include "nsIHttpChannelInternal.h"
97 #include "nsIClassOfService.h"
98 #include "nsCharSeparatedTokenizer.h"
99 #include "nsStreamListenerWrapper.h"
100 #include "nsITimedChannel.h"
101 #include "nsWrapperCacheInlines.h"
102 #include "nsZipArchive.h"
103 #include "mozilla/Preferences.h"
104 #include "private/pprio.h"
105 #include "XMLHttpRequestUpload.h"
107 // Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being
108 // replaced by FileCreatorHelper#CreateFileW.
109 #ifdef CreateFile
110 # undef CreateFile
111 #endif
113 using namespace mozilla::net;
115 namespace mozilla {
116 namespace dom {
118 // Maximum size that we'll grow an ArrayBuffer instead of doubling,
119 // once doubling reaches this threshold
120 const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH = 32 * 1024 * 1024;
121 // start at 32k to avoid lots of doubling right at the start
122 const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE = 32 * 1024;
123 // the maximum Content-Length that we'll preallocate. 1GB. Must fit
124 // in an int32_t!
125 const int32_t XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE =
126 1 * 1024 * 1024 * 1024LL;
128 namespace {
129 const nsLiteralString ProgressEventTypeStrings[] = {
130 u"loadstart"_ns, u"progress"_ns, u"error"_ns, u"abort"_ns,
131 u"timeout"_ns, u"load"_ns, u"loadend"_ns};
132 static_assert(MOZ_ARRAY_LENGTH(ProgressEventTypeStrings) ==
133 size_t(XMLHttpRequestMainThread::ProgressEventType::ENUM_MAX),
134 "Mismatched lengths for ProgressEventTypeStrings and "
135 "ProgressEventType enums");
137 const nsString kLiteralString_readystatechange = u"readystatechange"_ns;
138 const nsString kLiteralString_xmlhttprequest = u"xmlhttprequest"_ns;
139 const nsString kLiteralString_DOMContentLoaded = u"DOMContentLoaded"_ns;
140 const nsCString kLiteralString_charset = "charset"_ns;
141 const nsCString kLiteralString_UTF_8 = "UTF-8"_ns;
142 } // namespace
144 #define NS_PROGRESS_EVENT_INTERVAL 50
145 #define MAX_SYNC_TIMEOUT_WHEN_UNLOADING 10000 /* 10 secs */
147 NS_IMPL_ISUPPORTS(nsXHRParseEndListener, nsIDOMEventListener)
149 class nsResumeTimeoutsEvent : public Runnable {
150 public:
151 explicit nsResumeTimeoutsEvent(nsPIDOMWindowInner* aWindow)
152 : Runnable("dom::nsResumeTimeoutsEvent"), mWindow(aWindow) {}
154 NS_IMETHOD Run() override {
155 mWindow->Resume();
156 return NS_OK;
159 private:
160 nsCOMPtr<nsPIDOMWindowInner> mWindow;
163 // This helper function adds the given load flags to the request's existing
164 // load flags.
165 static void AddLoadFlags(nsIRequest* request, nsLoadFlags newFlags) {
166 nsLoadFlags flags;
167 request->GetLoadFlags(&flags);
168 flags |= newFlags;
169 request->SetLoadFlags(flags);
172 // We are in a sync event loop.
173 #define NOT_CALLABLE_IN_SYNC_SEND_RV \
174 if (mFlagSyncLooping || mEventDispatchingSuspended) { \
175 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT); \
176 return; \
179 /////////////////////////////////////////////
182 /////////////////////////////////////////////
184 bool XMLHttpRequestMainThread::sDontWarnAboutSyncXHR = false;
186 XMLHttpRequestMainThread::XMLHttpRequestMainThread(
187 nsIGlobalObject* aGlobalObject)
188 : XMLHttpRequest(aGlobalObject),
189 mResponseBodyDecodedPos(0),
190 mResponseType(XMLHttpRequestResponseType::_empty),
191 mRequestObserver(nullptr),
192 mState(XMLHttpRequest_Binding::UNSENT),
193 mFlagSynchronous(false),
194 mFlagAborted(false),
195 mFlagParseBody(false),
196 mFlagSyncLooping(false),
197 mFlagBackgroundRequest(false),
198 mFlagHadUploadListenersOnSend(false),
199 mFlagACwithCredentials(false),
200 mFlagTimedOut(false),
201 mFlagDeleted(false),
202 mFlagSend(false),
203 mUploadTransferred(0),
204 mUploadTotal(0),
205 mUploadComplete(true),
206 mProgressSinceLastProgressEvent(false),
207 mRequestSentTime(0),
208 mTimeoutMilliseconds(0),
209 mErrorLoad(ErrorType::eOK),
210 mErrorParsingXML(false),
211 mWaitingForOnStopRequest(false),
212 mProgressTimerIsActive(false),
213 mIsHtml(false),
214 mWarnAboutSyncHtml(false),
215 mLoadTotal(-1),
216 mLoadTransferred(0),
217 mIsSystem(false),
218 mIsAnon(false),
219 mFirstStartRequestSeen(false),
220 mInLoadProgressEvent(false),
221 mResultJSON(JS::UndefinedValue()),
222 mArrayBufferBuilder(new ArrayBufferBuilder()),
223 mResultArrayBuffer(nullptr),
224 mIsMappedArrayBuffer(false),
225 mXPCOMifier(nullptr),
226 mEventDispatchingSuspended(false),
227 mEofDecoded(false),
228 mDelayedDoneNotifier(nullptr) {
229 mozilla::HoldJSObjects(this);
232 XMLHttpRequestMainThread::~XMLHttpRequestMainThread() {
233 MOZ_ASSERT(
234 !mDelayedDoneNotifier,
235 "How can we have mDelayedDoneNotifier, which owns us, in destructor?");
237 mFlagDeleted = true;
239 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
240 mState == XMLHttpRequest_Binding::LOADING) {
241 Abort();
244 if (mParseEndListener) {
245 mParseEndListener->SetIsStale();
246 mParseEndListener = nullptr;
249 MOZ_ASSERT(!mFlagSyncLooping, "we rather crash than hang");
250 mFlagSyncLooping = false;
252 mozilla::DropJSObjects(this);
255 void XMLHttpRequestMainThread::Construct(
256 nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings,
257 bool aForWorker, nsIURI* aBaseURI /* = nullptr */,
258 nsILoadGroup* aLoadGroup /* = nullptr */,
259 PerformanceStorage* aPerformanceStorage /* = nullptr */,
260 nsICSPEventListener* aCSPEventListener /* = nullptr */) {
261 MOZ_ASSERT(aPrincipal);
262 mPrincipal = aPrincipal;
263 mBaseURI = aBaseURI;
264 mLoadGroup = aLoadGroup;
265 mCookieJarSettings = aCookieJarSettings;
266 mForWorker = aForWorker;
267 mPerformanceStorage = aPerformanceStorage;
268 mCSPEventListener = aCSPEventListener;
271 void XMLHttpRequestMainThread::InitParameters(bool aAnon, bool aSystem) {
272 if (!aAnon && !aSystem) {
273 return;
276 // Check for permissions.
277 // Chrome is always allowed access, so do the permission check only
278 // for non-chrome pages.
279 if (!IsSystemXHR() && aSystem) {
280 nsIGlobalObject* global = GetOwnerGlobal();
281 if (NS_WARN_IF(!global)) {
282 SetParameters(aAnon, false);
283 return;
286 nsIPrincipal* principal = global->PrincipalOrNull();
287 if (NS_WARN_IF(!principal)) {
288 SetParameters(aAnon, false);
289 return;
292 nsCOMPtr<nsIPermissionManager> permMgr =
293 components::PermissionManager::Service();
294 if (NS_WARN_IF(!permMgr)) {
295 SetParameters(aAnon, false);
296 return;
299 uint32_t permission;
300 nsresult rv = permMgr->TestPermissionFromPrincipal(
301 principal, "systemXHR"_ns, &permission);
302 if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) {
303 SetParameters(aAnon, false);
304 return;
308 SetParameters(aAnon, aSystem);
311 void XMLHttpRequestMainThread::SetClientInfoAndController(
312 const ClientInfo& aClientInfo,
313 const Maybe<ServiceWorkerDescriptor>& aController) {
314 mClientInfo.emplace(aClientInfo);
315 mController = aController;
318 void XMLHttpRequestMainThread::ResetResponse() {
319 mResponseXML = nullptr;
320 mResponseBody.Truncate();
321 TruncateResponseText();
322 mResponseBlobImpl = nullptr;
323 mResponseBlob = nullptr;
324 mBlobStorage = nullptr;
325 mResultArrayBuffer = nullptr;
326 mArrayBufferBuilder = new ArrayBufferBuilder();
327 mResultJSON.setUndefined();
328 mLoadTransferred = 0;
329 mResponseBodyDecodedPos = 0;
330 mEofDecoded = false;
333 void XMLHttpRequestMainThread::SetRequestObserver(
334 nsIRequestObserver* aObserver) {
335 mRequestObserver = aObserver;
338 NS_IMPL_CYCLE_COLLECTION_MULTI_ZONE_JSHOLDER_CLASS(XMLHttpRequestMainThread)
340 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestMainThread,
341 XMLHttpRequestEventTarget)
342 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
343 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
344 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML)
346 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener)
348 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseBlob)
349 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationCallbacks)
351 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink)
352 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink)
354 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
355 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
357 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestMainThread,
358 XMLHttpRequestEventTarget)
359 tmp->mResultArrayBuffer = nullptr;
360 tmp->mArrayBufferBuilder = nullptr;
361 tmp->mResultJSON.setUndefined();
362 tmp->mResponseBlobImpl = nullptr;
364 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
365 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
366 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML)
368 NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener)
370 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseBlob)
371 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationCallbacks)
373 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink)
374 NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink)
376 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
377 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
379 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestMainThread,
380 XMLHttpRequestEventTarget)
381 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
382 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultJSON)
383 NS_IMPL_CYCLE_COLLECTION_TRACE_END
385 bool XMLHttpRequestMainThread::IsCertainlyAliveForCC() const {
386 return mWaitingForOnStopRequest;
389 // QueryInterface implementation for XMLHttpRequestMainThread
390 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XMLHttpRequestMainThread)
391 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
392 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
393 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
394 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
395 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
396 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
397 NS_INTERFACE_MAP_ENTRY(nsINamed)
398 NS_INTERFACE_MAP_ENTRY(nsISizeOfEventTarget)
399 NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
401 NS_IMPL_ADDREF_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
402 NS_IMPL_RELEASE_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
404 void XMLHttpRequestMainThread::DisconnectFromOwner() {
405 XMLHttpRequestEventTarget::DisconnectFromOwner();
406 Abort();
409 size_t XMLHttpRequestMainThread::SizeOfEventTargetIncludingThis(
410 MallocSizeOf aMallocSizeOf) const {
411 size_t n = aMallocSizeOf(this);
412 n += mResponseBody.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
414 // Why is this safe? Because no-one else will report this string. The
415 // other possible sharers of this string are as follows.
417 // - The JS engine could hold copies if the JS code holds references, e.g.
418 // |var text = XHR.responseText|. However, those references will be via JS
419 // external strings, for which the JS memory reporter does *not* report the
420 // chars.
422 // - Binary extensions, but they're *extremely* unlikely to do any memory
423 // reporting.
425 n += mResponseText.SizeOfThis(aMallocSizeOf);
427 return n;
429 // Measurement of the following members may be added later if DMD finds it is
430 // worthwhile:
431 // - lots
434 static void LogMessage(
435 const char* aWarning, nsPIDOMWindowInner* aWindow,
436 const nsTArray<nsString>& aParams = nsTArray<nsString>()) {
437 nsCOMPtr<Document> doc;
438 if (aWindow) {
439 doc = aWindow->GetExtantDoc();
441 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, doc,
442 nsContentUtils::eDOM_PROPERTIES, aWarning,
443 aParams);
446 Document* XMLHttpRequestMainThread::GetResponseXML(ErrorResult& aRv) {
447 if (mResponseType != XMLHttpRequestResponseType::_empty &&
448 mResponseType != XMLHttpRequestResponseType::Document) {
449 aRv.ThrowInvalidStateError(
450 "responseXML is only available if responseType is '' or 'document'.");
451 return nullptr;
453 if (mWarnAboutSyncHtml) {
454 mWarnAboutSyncHtml = false;
455 LogMessage("HTMLSyncXHRWarning", GetOwner());
457 if (mState != XMLHttpRequest_Binding::DONE) {
458 return nullptr;
460 return mResponseXML;
464 * This piece copied from XMLDocument, we try to get the charset
465 * from HTTP headers.
467 nsresult XMLHttpRequestMainThread::DetectCharset() {
468 mDecoder = nullptr;
470 if (mResponseType != XMLHttpRequestResponseType::_empty &&
471 mResponseType != XMLHttpRequestResponseType::Text &&
472 mResponseType != XMLHttpRequestResponseType::Json) {
473 return NS_OK;
476 nsAutoCString charsetVal;
477 const Encoding* encoding;
478 bool ok = mChannel && NS_SUCCEEDED(mChannel->GetContentCharset(charsetVal)) &&
479 (encoding = Encoding::ForLabel(charsetVal));
480 if (!ok) {
481 // MS documentation states UTF-8 is default for responseText
482 encoding = UTF_8_ENCODING;
485 if (mResponseType == XMLHttpRequestResponseType::Json &&
486 encoding != UTF_8_ENCODING) {
487 // The XHR spec says only UTF-8 is supported for responseType == "json"
488 LogMessage("JSONCharsetWarning", GetOwner());
489 encoding = UTF_8_ENCODING;
492 // Only sniff the BOM for non-JSON responseTypes
493 if (mResponseType == XMLHttpRequestResponseType::Json) {
494 mDecoder = encoding->NewDecoderWithBOMRemoval();
495 } else {
496 mDecoder = encoding->NewDecoder();
499 return NS_OK;
502 nsresult XMLHttpRequestMainThread::AppendToResponseText(
503 Span<const uint8_t> aBuffer, bool aLast) {
504 // Call this with an empty buffer to send the decoder the signal
505 // that we have hit the end of the stream.
507 NS_ENSURE_STATE(mDecoder);
509 CheckedInt<size_t> destBufferLen =
510 mDecoder->MaxUTF16BufferLength(aBuffer.Length());
512 { // scope for holding the mutex that protects mResponseText
513 XMLHttpRequestStringWriterHelper helper(mResponseText);
515 uint32_t len = helper.Length();
517 destBufferLen += len;
518 if (!destBufferLen.isValid() || destBufferLen.value() > UINT32_MAX) {
519 return NS_ERROR_OUT_OF_MEMORY;
522 auto handleOrErr = helper.BulkWrite(destBufferLen.value());
523 if (handleOrErr.isErr()) {
524 return handleOrErr.unwrapErr();
527 auto handle = handleOrErr.unwrap();
529 uint32_t result;
530 size_t read;
531 size_t written;
532 bool hadErrors;
533 Tie(result, read, written, hadErrors) =
534 mDecoder->DecodeToUTF16(aBuffer, handle.AsSpan().From(len), aLast);
535 MOZ_ASSERT(result == kInputEmpty);
536 MOZ_ASSERT(read == aBuffer.Length());
537 len += written;
538 MOZ_ASSERT(len <= destBufferLen.value());
539 Unused << hadErrors;
540 handle.Finish(len, false);
541 } // release mutex
543 if (aLast) {
544 // Drop the finished decoder to avoid calling into a decoder
545 // that has finished.
546 mDecoder = nullptr;
547 mEofDecoded = true;
549 return NS_OK;
552 void XMLHttpRequestMainThread::GetResponseText(DOMString& aResponseText,
553 ErrorResult& aRv) {
554 MOZ_DIAGNOSTIC_ASSERT(!mForWorker);
556 XMLHttpRequestStringSnapshot snapshot;
557 GetResponseText(snapshot, aRv);
558 if (aRv.Failed()) {
559 return;
562 if (!snapshot.GetAsString(aResponseText)) {
563 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
564 return;
568 void XMLHttpRequestMainThread::GetResponseText(
569 XMLHttpRequestStringSnapshot& aSnapshot, ErrorResult& aRv) {
570 aSnapshot.Reset();
572 if (mResponseType != XMLHttpRequestResponseType::_empty &&
573 mResponseType != XMLHttpRequestResponseType::Text) {
574 aRv.ThrowInvalidStateError(
575 "responseText is only available if responseType is '' or 'text'.");
576 return;
579 if (mState != XMLHttpRequest_Binding::LOADING &&
580 mState != XMLHttpRequest_Binding::DONE) {
581 return;
584 // Main Fetch step 18 requires to ignore body for head/connect methods.
585 if (mRequestMethod.EqualsLiteral("HEAD") ||
586 mRequestMethod.EqualsLiteral("CONNECT")) {
587 return;
590 // We only decode text lazily if we're also parsing to a doc.
591 // Also, if we've decoded all current data already, then no need to decode
592 // more.
593 if ((!mResponseXML && !mErrorParsingXML) ||
594 (mResponseBodyDecodedPos == mResponseBody.Length() &&
595 (mState != XMLHttpRequest_Binding::DONE || mEofDecoded))) {
596 mResponseText.CreateSnapshot(aSnapshot);
597 return;
600 MatchCharsetAndDecoderToResponseDocument();
602 MOZ_ASSERT(mResponseBodyDecodedPos < mResponseBody.Length() ||
603 mState == XMLHttpRequest_Binding::DONE,
604 "Unexpected mResponseBodyDecodedPos");
605 Span<const uint8_t> span = mResponseBody;
606 aRv = AppendToResponseText(span.From(mResponseBodyDecodedPos),
607 mState == XMLHttpRequest_Binding::DONE);
608 if (aRv.Failed()) {
609 return;
612 mResponseBodyDecodedPos = mResponseBody.Length();
614 if (mEofDecoded) {
615 // Free memory buffer which we no longer need
616 mResponseBody.Truncate();
617 mResponseBodyDecodedPos = 0;
620 mResponseText.CreateSnapshot(aSnapshot);
623 nsresult XMLHttpRequestMainThread::CreateResponseParsedJSON(JSContext* aCx) {
624 if (!aCx) {
625 return NS_ERROR_FAILURE;
628 nsAutoString string;
629 nsresult rv = GetResponseTextForJSON(string);
630 if (NS_WARN_IF(NS_FAILED(rv))) {
631 return rv;
634 // The Unicode converter has already zapped the BOM if there was one
635 JS::Rooted<JS::Value> value(aCx);
636 if (!JS_ParseJSON(aCx, string.BeginReading(), string.Length(), &value)) {
637 return NS_ERROR_FAILURE;
640 mResultJSON = value;
641 return NS_OK;
644 void XMLHttpRequestMainThread::SetResponseType(
645 XMLHttpRequestResponseType aResponseType, ErrorResult& aRv) {
646 NOT_CALLABLE_IN_SYNC_SEND_RV
648 if (mState == XMLHttpRequest_Binding::LOADING ||
649 mState == XMLHttpRequest_Binding::DONE) {
650 aRv.ThrowInvalidStateError(
651 "Cannot set 'responseType' property on XMLHttpRequest after 'send()' "
652 "(when its state is LOADING or DONE).");
653 return;
656 // sync request is not allowed setting responseType in window context
657 if (HasOrHasHadOwner() && mState != XMLHttpRequest_Binding::UNSENT &&
658 mFlagSynchronous) {
659 LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
660 aRv.ThrowInvalidAccessError(
661 "synchronous XMLHttpRequests do not support timeout and responseType");
662 return;
665 // Set the responseType attribute's value to the given value.
666 SetResponseTypeRaw(aResponseType);
669 void XMLHttpRequestMainThread::GetResponse(
670 JSContext* aCx, JS::MutableHandle<JS::Value> aResponse, ErrorResult& aRv) {
671 MOZ_DIAGNOSTIC_ASSERT(!mForWorker);
673 switch (mResponseType) {
674 case XMLHttpRequestResponseType::_empty:
675 case XMLHttpRequestResponseType::Text: {
676 DOMString str;
677 GetResponseText(str, aRv);
678 if (aRv.Failed()) {
679 return;
681 if (!xpc::StringToJsval(aCx, str, aResponse)) {
682 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
684 return;
687 case XMLHttpRequestResponseType::Arraybuffer: {
688 if (mState != XMLHttpRequest_Binding::DONE) {
689 aResponse.setNull();
690 return;
693 if (!mResultArrayBuffer) {
694 mResultArrayBuffer = mArrayBufferBuilder->TakeArrayBuffer(aCx);
695 if (!mResultArrayBuffer) {
696 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
697 return;
700 aResponse.setObject(*mResultArrayBuffer);
701 return;
703 case XMLHttpRequestResponseType::Blob: {
704 if (mState != XMLHttpRequest_Binding::DONE) {
705 aResponse.setNull();
706 return;
709 if (!mResponseBlobImpl) {
710 aResponse.setNull();
711 return;
714 if (!mResponseBlob) {
715 mResponseBlob = Blob::Create(GetOwnerGlobal(), mResponseBlobImpl);
718 if (!GetOrCreateDOMReflector(aCx, mResponseBlob, aResponse)) {
719 aResponse.setNull();
722 return;
724 case XMLHttpRequestResponseType::Document: {
725 if (!mResponseXML || mState != XMLHttpRequest_Binding::DONE) {
726 aResponse.setNull();
727 return;
730 aRv =
731 nsContentUtils::WrapNative(aCx, ToSupports(mResponseXML), aResponse);
732 return;
734 case XMLHttpRequestResponseType::Json: {
735 if (mState != XMLHttpRequest_Binding::DONE) {
736 aResponse.setNull();
737 return;
740 if (mResultJSON.isUndefined()) {
741 aRv = CreateResponseParsedJSON(aCx);
742 TruncateResponseText();
743 if (aRv.Failed()) {
744 // Per spec, errors aren't propagated. null is returned instead.
745 aRv = NS_OK;
746 // It would be nice to log the error to the console. That's hard to
747 // do without calling window.onerror as a side effect, though.
748 JS_ClearPendingException(aCx);
749 mResultJSON.setNull();
752 aResponse.set(mResultJSON);
753 return;
755 default:
756 NS_ERROR("Should not happen");
759 aResponse.setNull();
762 already_AddRefed<BlobImpl> XMLHttpRequestMainThread::GetResponseBlobImpl() {
763 MOZ_DIAGNOSTIC_ASSERT(mForWorker);
764 MOZ_DIAGNOSTIC_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
766 if (mState != XMLHttpRequest_Binding::DONE) {
767 return nullptr;
770 RefPtr<BlobImpl> blobImpl = mResponseBlobImpl;
771 return blobImpl.forget();
774 already_AddRefed<ArrayBufferBuilder>
775 XMLHttpRequestMainThread::GetResponseArrayBufferBuilder() {
776 MOZ_DIAGNOSTIC_ASSERT(mForWorker);
777 MOZ_DIAGNOSTIC_ASSERT(mResponseType ==
778 XMLHttpRequestResponseType::Arraybuffer);
780 if (mState != XMLHttpRequest_Binding::DONE) {
781 return nullptr;
784 RefPtr<ArrayBufferBuilder> builder = mArrayBufferBuilder;
785 return builder.forget();
788 nsresult XMLHttpRequestMainThread::GetResponseTextForJSON(nsAString& aString) {
789 if (mState != XMLHttpRequest_Binding::DONE) {
790 aString.SetIsVoid(true);
791 return NS_OK;
794 if (!mResponseText.GetAsString(aString)) {
795 return NS_ERROR_OUT_OF_MEMORY;
798 return NS_OK;
801 bool XMLHttpRequestMainThread::IsCrossSiteCORSRequest() const {
802 if (!mChannel) {
803 return false;
806 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
807 return loadInfo->GetTainting() == LoadTainting::CORS;
810 bool XMLHttpRequestMainThread::IsDeniedCrossSiteCORSRequest() {
811 if (IsCrossSiteCORSRequest()) {
812 nsresult rv;
813 mChannel->GetStatus(&rv);
814 if (NS_FAILED(rv)) {
815 return true;
818 return false;
821 void XMLHttpRequestMainThread::GetResponseURL(nsAString& aUrl) {
822 aUrl.Truncate();
824 if ((mState == XMLHttpRequest_Binding::UNSENT ||
825 mState == XMLHttpRequest_Binding::OPENED) ||
826 !mChannel) {
827 return;
830 // Make sure we don't leak responseURL information from denied cross-site
831 // requests.
832 if (IsDeniedCrossSiteCORSRequest()) {
833 return;
836 nsCOMPtr<nsIURI> responseUrl;
837 if (NS_FAILED(NS_GetFinalChannelURI(mChannel, getter_AddRefs(responseUrl)))) {
838 return;
841 nsAutoCString temp;
842 responseUrl->GetSpecIgnoringRef(temp);
843 CopyUTF8toUTF16(temp, aUrl);
846 uint32_t XMLHttpRequestMainThread::GetStatus(ErrorResult& aRv) {
847 // Make sure we don't leak status information from denied cross-site
848 // requests.
849 if (IsDeniedCrossSiteCORSRequest()) {
850 return 0;
853 if (mState == XMLHttpRequest_Binding::UNSENT ||
854 mState == XMLHttpRequest_Binding::OPENED) {
855 return 0;
858 if (mErrorLoad != ErrorType::eOK) {
859 // Let's simulate the http protocol for jar/app requests:
860 nsCOMPtr<nsIJARChannel> jarChannel = GetCurrentJARChannel();
861 if (jarChannel) {
862 nsresult status;
863 mChannel->GetStatus(&status);
865 if (status == NS_ERROR_FILE_NOT_FOUND) {
866 return 404; // Not Found
867 } else {
868 return 500; // Internal Error
872 return 0;
875 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
876 if (!httpChannel) {
877 // Pretend like we got a 200 response, since our load was successful
878 return 200;
881 uint32_t status;
882 nsresult rv = httpChannel->GetResponseStatus(&status);
883 if (NS_FAILED(rv)) {
884 status = 0;
887 return status;
890 void XMLHttpRequestMainThread::GetStatusText(nsACString& aStatusText,
891 ErrorResult& aRv) {
892 // Return an empty status text on all error loads.
893 aStatusText.Truncate();
895 // Make sure we don't leak status information from denied cross-site
896 // requests.
897 if (IsDeniedCrossSiteCORSRequest()) {
898 return;
901 // Check the current XHR state to see if it is valid to obtain the statusText
902 // value. This check is to prevent the status text for redirects from being
903 // available before all the redirects have been followed and HTTP headers have
904 // been received.
905 if (mState == XMLHttpRequest_Binding::UNSENT ||
906 mState == XMLHttpRequest_Binding::OPENED) {
907 return;
910 if (mErrorLoad != ErrorType::eOK) {
911 return;
914 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
915 if (httpChannel) {
916 Unused << httpChannel->GetResponseStatusText(aStatusText);
917 } else {
918 aStatusText.AssignLiteral("OK");
922 void XMLHttpRequestMainThread::TerminateOngoingFetch() {
923 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
924 mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
925 mState == XMLHttpRequest_Binding::LOADING) {
926 CloseRequest();
930 void XMLHttpRequestMainThread::CloseRequest() {
931 mWaitingForOnStopRequest = false;
932 mErrorLoad = ErrorType::eTerminated;
933 if (mChannel) {
934 mChannel->Cancel(NS_BINDING_ABORTED);
936 if (mTimeoutTimer) {
937 mTimeoutTimer->Cancel();
941 void XMLHttpRequestMainThread::CloseRequestWithError(
942 const ProgressEventType aType) {
943 CloseRequest();
945 ResetResponse();
947 // If we're in the destructor, don't risk dispatching an event.
948 if (mFlagDeleted) {
949 mFlagSyncLooping = false;
950 return;
953 if (mState != XMLHttpRequest_Binding::UNSENT &&
954 !(mState == XMLHttpRequest_Binding::OPENED && !mFlagSend) &&
955 mState != XMLHttpRequest_Binding::DONE) {
956 ChangeState(XMLHttpRequest_Binding::DONE, true);
958 if (!mFlagSyncLooping) {
959 if (mUpload && !mUploadComplete) {
960 mUploadComplete = true;
961 DispatchProgressEvent(mUpload, aType, 0, -1);
963 DispatchProgressEvent(this, aType, 0, -1);
967 // The ChangeState call above calls onreadystatechange handlers which
968 // if they load a new url will cause XMLHttpRequestMainThread::Open to clear
969 // the abort state bit. If this occurs we're not uninitialized (bug 361773).
970 if (mFlagAborted) {
971 ChangeState(XMLHttpRequest_Binding::UNSENT, false); // IE seems to do it
974 mFlagSyncLooping = false;
977 void XMLHttpRequestMainThread::RequestErrorSteps(
978 const ProgressEventType aEventType, const nsresult aOptionalException,
979 ErrorResult& aRv) {
980 // Step 1
981 mState = XMLHttpRequest_Binding::DONE;
983 StopProgressEventTimer();
985 // Step 2
986 mFlagSend = false;
988 // Step 3
989 ResetResponse();
991 // If we're in the destructor, don't risk dispatching an event.
992 if (mFlagDeleted) {
993 mFlagSyncLooping = false;
994 return;
997 // Step 4
998 if (mFlagSynchronous && NS_FAILED(aOptionalException)) {
999 aRv.Throw(aOptionalException);
1000 return;
1003 // Step 5
1004 FireReadystatechangeEvent();
1006 // Step 6
1007 if (mUpload && !mUploadComplete) {
1008 // Step 6-1
1009 mUploadComplete = true;
1011 // Step 6-2
1012 if (mFlagHadUploadListenersOnSend) {
1013 // Steps 6-3, 6-4 (loadend is fired for us)
1014 DispatchProgressEvent(mUpload, aEventType, 0, -1);
1018 // Steps 7 and 8 (loadend is fired for us)
1019 DispatchProgressEvent(this, aEventType, 0, -1);
1022 void XMLHttpRequestMainThread::Abort(ErrorResult& aRv) {
1023 NOT_CALLABLE_IN_SYNC_SEND_RV
1024 AbortInternal(aRv);
1027 void XMLHttpRequestMainThread::AbortInternal(ErrorResult& aRv) {
1028 mFlagAborted = true;
1029 DisconnectDoneNotifier();
1031 // Step 1
1032 TerminateOngoingFetch();
1034 // Step 2
1035 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
1036 mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
1037 mState == XMLHttpRequest_Binding::LOADING) {
1038 RequestErrorSteps(ProgressEventType::abort, NS_OK, aRv);
1041 // Step 3
1042 if (mState == XMLHttpRequest_Binding::DONE) {
1043 ChangeState(XMLHttpRequest_Binding::UNSENT,
1044 false); // no ReadystateChange event
1047 mFlagSyncLooping = false;
1050 /*Method that checks if it is safe to expose a header value to the client.
1051 It is used to check what headers are exposed for CORS requests.*/
1052 bool XMLHttpRequestMainThread::IsSafeHeader(
1053 const nsACString& aHeader, NotNull<nsIHttpChannel*> aHttpChannel) const {
1054 // See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
1055 if (!IsSystemXHR() && nsContentUtils::IsForbiddenResponseHeader(aHeader)) {
1056 NS_WARNING("blocked access to response header");
1057 return false;
1059 // if this is not a CORS call all headers are safe
1060 if (!IsCrossSiteCORSRequest()) {
1061 return true;
1063 // Check for dangerous headers
1064 // Make sure we don't leak header information from denied cross-site
1065 // requests.
1066 if (mChannel) {
1067 nsresult status;
1068 mChannel->GetStatus(&status);
1069 if (NS_FAILED(status)) {
1070 return false;
1073 const char* kCrossOriginSafeHeaders[] = {
1074 "cache-control", "content-language", "content-type", "content-length",
1075 "expires", "last-modified", "pragma"};
1076 for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
1077 if (aHeader.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
1078 return true;
1081 nsAutoCString headerVal;
1082 // The "Access-Control-Expose-Headers" header contains a comma separated
1083 // list of method names.
1084 Unused << aHttpChannel->GetResponseHeader("Access-Control-Expose-Headers"_ns,
1085 headerVal);
1086 bool isSafe = false;
1087 for (const nsACString& token :
1088 nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
1089 if (token.IsEmpty()) {
1090 continue;
1092 if (!NS_IsValidHTTPToken(token)) {
1093 return false;
1096 if (token.EqualsLiteral("*") && !mFlagACwithCredentials) {
1097 isSafe = true;
1098 } else if (aHeader.Equals(token, nsCaseInsensitiveCStringComparator)) {
1099 isSafe = true;
1103 return isSafe;
1106 void XMLHttpRequestMainThread::GetAllResponseHeaders(
1107 nsACString& aResponseHeaders, ErrorResult& aRv) {
1108 NOT_CALLABLE_IN_SYNC_SEND_RV
1110 aResponseHeaders.Truncate();
1112 // If the state is UNSENT or OPENED,
1113 // return the empty string and terminate these steps.
1114 if (mState == XMLHttpRequest_Binding::UNSENT ||
1115 mState == XMLHttpRequest_Binding::OPENED) {
1116 return;
1119 if (mErrorLoad != ErrorType::eOK) {
1120 return;
1123 if (nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel()) {
1124 RefPtr<nsHeaderVisitor> visitor =
1125 new nsHeaderVisitor(*this, WrapNotNull(httpChannel));
1126 if (NS_SUCCEEDED(httpChannel->VisitResponseHeaders(visitor))) {
1127 aResponseHeaders = visitor->Headers();
1129 return;
1132 if (!mChannel) {
1133 return;
1136 // Even non-http channels supply content type.
1137 nsAutoCString value;
1138 if (NS_SUCCEEDED(mChannel->GetContentType(value))) {
1139 aResponseHeaders.AppendLiteral("Content-Type: ");
1140 aResponseHeaders.Append(value);
1141 if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) {
1142 aResponseHeaders.AppendLiteral(";charset=");
1143 aResponseHeaders.Append(value);
1145 aResponseHeaders.AppendLiteral("\r\n");
1148 // Don't provide Content-Length for data URIs
1149 nsCOMPtr<nsIURI> uri;
1150 if (NS_FAILED(mChannel->GetURI(getter_AddRefs(uri))) ||
1151 !uri->SchemeIs("data")) {
1152 int64_t length;
1153 if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1154 aResponseHeaders.AppendLiteral("Content-Length: ");
1155 aResponseHeaders.AppendInt(length);
1156 aResponseHeaders.AppendLiteral("\r\n");
1161 void XMLHttpRequestMainThread::GetResponseHeader(const nsACString& header,
1162 nsACString& _retval,
1163 ErrorResult& aRv) {
1164 NOT_CALLABLE_IN_SYNC_SEND_RV
1166 _retval.SetIsVoid(true);
1168 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
1170 if (!httpChannel) {
1171 // If the state is UNSENT or OPENED,
1172 // return null and terminate these steps.
1173 if (mState == XMLHttpRequest_Binding::UNSENT ||
1174 mState == XMLHttpRequest_Binding::OPENED) {
1175 return;
1178 // Even non-http channels supply content type and content length.
1179 // Remember we don't leak header information from denied cross-site
1180 // requests. However, we handle file: and blob: URLs for blob response
1181 // types by canceling them with a specific error, so we have to allow
1182 // them to pass through this check.
1183 nsresult status;
1184 if (!mChannel || NS_FAILED(mChannel->GetStatus(&status)) ||
1185 (NS_FAILED(status) && status != NS_ERROR_FILE_ALREADY_EXISTS)) {
1186 return;
1189 // Content Type:
1190 if (header.LowerCaseEqualsASCII("content-type")) {
1191 if (NS_FAILED(mChannel->GetContentType(_retval))) {
1192 // Means no content type
1193 _retval.SetIsVoid(true);
1194 return;
1197 nsCString value;
1198 if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) &&
1199 !value.IsEmpty()) {
1200 _retval.AppendLiteral(";charset=");
1201 _retval.Append(value);
1205 // Content Length:
1206 else if (header.LowerCaseEqualsASCII("content-length")) {
1207 int64_t length;
1208 if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1209 _retval.AppendInt(length);
1213 return;
1216 // Check for dangerous headers
1217 if (!IsSafeHeader(header, WrapNotNull(httpChannel))) {
1218 return;
1221 aRv = httpChannel->GetResponseHeader(header, _retval);
1222 if (aRv.ErrorCodeIs(NS_ERROR_NOT_AVAILABLE)) {
1223 // Means no header
1224 _retval.SetIsVoid(true);
1225 aRv.SuppressException();
1229 already_AddRefed<nsILoadGroup> XMLHttpRequestMainThread::GetLoadGroup() const {
1230 if (mFlagBackgroundRequest) {
1231 return nullptr;
1234 if (mLoadGroup) {
1235 nsCOMPtr<nsILoadGroup> ref = mLoadGroup;
1236 return ref.forget();
1239 Document* doc = GetDocumentIfCurrent();
1240 if (doc) {
1241 return doc->GetDocumentLoadGroup();
1244 return nullptr;
1247 nsresult XMLHttpRequestMainThread::FireReadystatechangeEvent() {
1248 MOZ_ASSERT(mState != XMLHttpRequest_Binding::UNSENT);
1249 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1250 event->InitEvent(kLiteralString_readystatechange, false, false);
1251 // We assume anyone who managed to call CreateReadystatechangeEvent is trusted
1252 event->SetTrusted(true);
1253 DispatchOrStoreEvent(this, event);
1254 return NS_OK;
1257 void XMLHttpRequestMainThread::DispatchProgressEvent(
1258 DOMEventTargetHelper* aTarget, const ProgressEventType aType,
1259 int64_t aLoaded, int64_t aTotal) {
1260 NS_ASSERTION(aTarget, "null target");
1262 if (NS_FAILED(CheckCurrentGlobalCorrectness()) ||
1263 (!AllowUploadProgress() && aTarget == mUpload)) {
1264 return;
1267 // If blocked by CORS, zero-out the stats on progress events
1268 // and never fire "progress" or "load" events at all.
1269 if (IsDeniedCrossSiteCORSRequest()) {
1270 if (aType == ProgressEventType::progress ||
1271 aType == ProgressEventType::load) {
1272 return;
1274 aLoaded = 0;
1275 aTotal = -1;
1278 if (aType == ProgressEventType::progress) {
1279 mInLoadProgressEvent = true;
1282 ProgressEventInit init;
1283 init.mBubbles = false;
1284 init.mCancelable = false;
1285 init.mLengthComputable = aTotal != -1; // XHR spec step 6.1
1286 init.mLoaded = aLoaded;
1287 init.mTotal = (aTotal == -1) ? 0 : aTotal;
1289 const nsAString& typeString = ProgressEventTypeStrings[(uint8_t)aType];
1290 RefPtr<ProgressEvent> event =
1291 ProgressEvent::Constructor(aTarget, typeString, init);
1292 event->SetTrusted(true);
1294 DispatchOrStoreEvent(aTarget, event);
1296 if (aType == ProgressEventType::progress) {
1297 mInLoadProgressEvent = false;
1300 // If we're sending a load, error, timeout or abort event, then
1301 // also dispatch the subsequent loadend event.
1302 if (aType == ProgressEventType::load || aType == ProgressEventType::error ||
1303 aType == ProgressEventType::timeout ||
1304 aType == ProgressEventType::abort) {
1305 DispatchProgressEvent(aTarget, ProgressEventType::loadend, aLoaded, aTotal);
1309 void XMLHttpRequestMainThread::DispatchOrStoreEvent(
1310 DOMEventTargetHelper* aTarget, Event* aEvent) {
1311 MOZ_ASSERT(aTarget);
1312 MOZ_ASSERT(aEvent);
1314 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
1315 return;
1318 if (mEventDispatchingSuspended) {
1319 PendingEvent* event = mPendingEvents.AppendElement();
1320 event->mTarget = aTarget;
1321 event->mEvent = aEvent;
1322 return;
1325 aTarget->DispatchEvent(*aEvent);
1328 void XMLHttpRequestMainThread::SuspendEventDispatching() {
1329 MOZ_ASSERT(!mEventDispatchingSuspended);
1330 mEventDispatchingSuspended = true;
1333 void XMLHttpRequestMainThread::ResumeEventDispatching() {
1334 MOZ_ASSERT(mEventDispatchingSuspended);
1335 mEventDispatchingSuspended = false;
1337 nsTArray<PendingEvent> pendingEvents = std::move(mPendingEvents);
1339 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
1340 return;
1343 for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
1344 pendingEvents[i].mTarget->DispatchEvent(*pendingEvents[i].mEvent);
1348 already_AddRefed<nsIHttpChannel>
1349 XMLHttpRequestMainThread::GetCurrentHttpChannel() {
1350 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
1351 return httpChannel.forget();
1354 already_AddRefed<nsIJARChannel>
1355 XMLHttpRequestMainThread::GetCurrentJARChannel() {
1356 nsCOMPtr<nsIJARChannel> appChannel = do_QueryInterface(mChannel);
1357 return appChannel.forget();
1360 bool XMLHttpRequestMainThread::IsSystemXHR() const {
1361 return mIsSystem || mPrincipal->IsSystemPrincipal();
1364 bool XMLHttpRequestMainThread::InUploadPhase() const {
1365 // We're in the upload phase while our state is OPENED.
1366 return mState == XMLHttpRequest_Binding::OPENED;
1369 // This case is hit when the async parameter is outright omitted, which
1370 // should set it to true (and the username and password to null).
1371 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1372 const nsAString& aUrl, ErrorResult& aRv) {
1373 Open(aMethod, aUrl, true, VoidString(), VoidString(), aRv);
1376 // This case is hit when the async parameter is specified, even if the
1377 // JS value was "undefined" (which due to legacy reasons should be
1378 // treated as true, which is how it will already be passed in here).
1379 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1380 const nsAString& aUrl, bool aAsync,
1381 const nsAString& aUsername,
1382 const nsAString& aPassword,
1383 ErrorResult& aRv) {
1384 Open(aMethod, NS_ConvertUTF16toUTF8(aUrl), aAsync, aUsername, aPassword, aRv);
1387 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1388 const nsACString& aUrl, bool aAsync,
1389 const nsAString& aUsername,
1390 const nsAString& aPassword,
1391 ErrorResult& aRv) {
1392 NOT_CALLABLE_IN_SYNC_SEND_RV
1394 // Gecko-specific
1395 if (!aAsync && !DontWarnAboutSyncXHR() && GetOwner() &&
1396 GetOwner()->GetExtantDoc()) {
1397 GetOwner()->GetExtantDoc()->WarnOnceAbout(
1398 DeprecatedOperations::eSyncXMLHttpRequest);
1401 Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC,
1402 aAsync ? 0 : 1);
1404 // Step 1
1405 nsCOMPtr<Document> responsibleDocument = GetDocumentIfCurrent();
1406 if (!responsibleDocument) {
1407 // This could be because we're no longer current or because we're in some
1408 // non-window context...
1409 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
1410 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
1411 return;
1414 if (!mPrincipal) {
1415 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
1416 return;
1419 // Gecko-specific
1420 if (!aAsync && responsibleDocument && GetOwner()) {
1421 // We have no extant document during unload, so the above general
1422 // syncXHR warning will not display. But we do want to display a
1423 // recommendation to use sendBeacon instead of syncXHR during unload.
1424 nsCOMPtr<nsIDocShell> shell = responsibleDocument->GetDocShell();
1425 if (shell) {
1426 bool inUnload = false;
1427 shell->GetIsInUnload(&inUnload);
1428 if (inUnload) {
1429 LogMessage("UseSendBeaconDuringUnloadAndPagehideWarning", GetOwner());
1434 // Steps 2-4
1435 nsAutoCString method;
1436 aRv = FetchUtil::GetValidRequestMethod(aMethod, method);
1437 if (NS_WARN_IF(aRv.Failed())) {
1438 return;
1441 // Steps 5-6
1442 nsIURI* baseURI = nullptr;
1443 if (mBaseURI) {
1444 baseURI = mBaseURI;
1445 } else if (responsibleDocument) {
1446 baseURI = responsibleDocument->GetBaseURI();
1449 // Use the responsible document's encoding for the URL if we have one,
1450 // except for dedicated workers. Use UTF-8 otherwise.
1451 NotNull<const Encoding*> originCharset = UTF_8_ENCODING;
1452 if (responsibleDocument &&
1453 responsibleDocument->NodePrincipal() == mPrincipal) {
1454 originCharset = responsibleDocument->GetDocumentCharacterSet();
1457 nsCOMPtr<nsIURI> parsedURL;
1458 nsresult rv =
1459 NS_NewURI(getter_AddRefs(parsedURL), aUrl, originCharset, baseURI);
1460 if (NS_FAILED(rv)) {
1461 if (rv == NS_ERROR_MALFORMED_URI) {
1462 aRv.Throw(NS_ERROR_DOM_MALFORMED_URI);
1463 return;
1465 aRv.Throw(rv);
1466 return;
1468 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
1469 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
1470 return;
1473 // Step 7
1474 // This is already handled by the other Open() method, which passes
1475 // username and password in as NullStrings.
1477 // Step 8
1478 nsAutoCString host;
1479 parsedURL->GetHost(host);
1480 if (!host.IsEmpty() && (!aUsername.IsVoid() || !aPassword.IsVoid())) {
1481 auto mutator = NS_MutateURI(parsedURL);
1482 if (!aUsername.IsVoid()) {
1483 mutator.SetUsername(NS_ConvertUTF16toUTF8(aUsername));
1485 if (!aPassword.IsVoid()) {
1486 mutator.SetPassword(NS_ConvertUTF16toUTF8(aPassword));
1488 Unused << mutator.Finalize(parsedURL);
1491 // Step 9
1492 if (!aAsync && HasOrHasHadOwner() &&
1493 (mTimeoutMilliseconds ||
1494 mResponseType != XMLHttpRequestResponseType::_empty)) {
1495 if (mTimeoutMilliseconds) {
1496 LogMessage("TimeoutSyncXHRWarning", GetOwner());
1498 if (mResponseType != XMLHttpRequestResponseType::_empty) {
1499 LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
1501 aRv.ThrowInvalidAccessError(
1502 "synchronous XMLHttpRequests do not support timeout and responseType");
1503 return;
1506 // Step 10
1507 TerminateOngoingFetch();
1509 // Step 11
1510 // timeouts are handled without a flag
1511 DisconnectDoneNotifier();
1512 mFlagSend = false;
1513 mRequestMethod.Assign(method);
1514 mRequestURL = parsedURL;
1515 mFlagSynchronous = !aAsync;
1516 mAuthorRequestHeaders.Clear();
1517 ResetResponse();
1519 // Gecko-specific
1520 mFlagHadUploadListenersOnSend = false;
1521 mFlagAborted = false;
1522 mFlagTimedOut = false;
1523 mDecoder = nullptr;
1525 // Per spec we should only create the channel on send(), but we have internal
1526 // code that relies on the channel being created now, and that code is not
1527 // always IsSystemXHR(). However, we're not supposed to throw channel-creation
1528 // errors during open(), so we silently ignore those here.
1529 CreateChannel();
1531 // Step 12
1532 if (mState != XMLHttpRequest_Binding::OPENED) {
1533 mState = XMLHttpRequest_Binding::OPENED;
1534 FireReadystatechangeEvent();
1538 void XMLHttpRequestMainThread::SetOriginAttributes(
1539 const OriginAttributesDictionary& aAttrs) {
1540 MOZ_ASSERT((mState == XMLHttpRequest_Binding::OPENED) && !mFlagSend);
1542 OriginAttributes attrs(aAttrs);
1544 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
1545 loadInfo->SetOriginAttributes(attrs);
1549 * "Copy" from a stream.
1551 nsresult XMLHttpRequestMainThread::StreamReaderFunc(
1552 nsIInputStream* in, void* closure, const char* fromRawSegment,
1553 uint32_t toOffset, uint32_t count, uint32_t* writeCount) {
1554 XMLHttpRequestMainThread* xmlHttpRequest =
1555 static_cast<XMLHttpRequestMainThread*>(closure);
1556 if (!xmlHttpRequest || !writeCount) {
1557 NS_WARNING(
1558 "XMLHttpRequest cannot read from stream: no closure or writeCount");
1559 return NS_ERROR_FAILURE;
1562 nsresult rv = NS_OK;
1564 if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Blob) {
1565 xmlHttpRequest->MaybeCreateBlobStorage();
1566 rv = xmlHttpRequest->mBlobStorage->Append(fromRawSegment, count);
1567 } else if (xmlHttpRequest->mResponseType ==
1568 XMLHttpRequestResponseType::Arraybuffer &&
1569 !xmlHttpRequest->mIsMappedArrayBuffer) {
1570 // get the initial capacity to something reasonable to avoid a bunch of
1571 // reallocs right at the start
1572 if (xmlHttpRequest->mArrayBufferBuilder->Capacity() == 0)
1573 xmlHttpRequest->mArrayBufferBuilder->SetCapacity(
1574 std::max(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE));
1576 if (NS_WARN_IF(!xmlHttpRequest->mArrayBufferBuilder->Append(
1577 reinterpret_cast<const uint8_t*>(fromRawSegment), count,
1578 XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH))) {
1579 return NS_ERROR_OUT_OF_MEMORY;
1582 } else if (xmlHttpRequest->mResponseType ==
1583 XMLHttpRequestResponseType::_empty &&
1584 xmlHttpRequest->mResponseXML) {
1585 // Copy for our own use
1586 if (!xmlHttpRequest->mResponseBody.Append(fromRawSegment, count,
1587 fallible)) {
1588 return NS_ERROR_OUT_OF_MEMORY;
1590 } else if (xmlHttpRequest->mResponseType ==
1591 XMLHttpRequestResponseType::_empty ||
1592 xmlHttpRequest->mResponseType ==
1593 XMLHttpRequestResponseType::Text ||
1594 xmlHttpRequest->mResponseType ==
1595 XMLHttpRequestResponseType::Json) {
1596 MOZ_ASSERT(!xmlHttpRequest->mResponseXML,
1597 "We shouldn't be parsing a doc here");
1598 rv = xmlHttpRequest->AppendToResponseText(
1599 AsBytes(Span(fromRawSegment, count)));
1600 if (NS_WARN_IF(NS_FAILED(rv))) {
1601 return rv;
1605 if (xmlHttpRequest->mFlagParseBody) {
1606 // Give the same data to the parser.
1608 // We need to wrap the data in a new lightweight stream and pass that
1609 // to the parser, because calling ReadSegments() recursively on the same
1610 // stream is not supported.
1611 nsCOMPtr<nsIInputStream> copyStream;
1612 rv = NS_NewByteInputStream(getter_AddRefs(copyStream),
1613 Span(fromRawSegment, count),
1614 NS_ASSIGNMENT_DEPEND);
1616 if (NS_SUCCEEDED(rv) && xmlHttpRequest->mXMLParserStreamListener) {
1617 NS_ASSERTION(copyStream, "NS_NewByteInputStream lied");
1618 nsresult parsingResult =
1619 xmlHttpRequest->mXMLParserStreamListener->OnDataAvailable(
1620 xmlHttpRequest->mChannel, copyStream, toOffset, count);
1622 // No use to continue parsing if we failed here, but we
1623 // should still finish reading the stream
1624 if (NS_FAILED(parsingResult)) {
1625 xmlHttpRequest->mFlagParseBody = false;
1630 if (NS_SUCCEEDED(rv)) {
1631 *writeCount = count;
1632 } else {
1633 *writeCount = 0;
1636 return rv;
1639 namespace {
1641 void GetBlobURIFromChannel(nsIRequest* aRequest, nsIURI** aURI) {
1642 MOZ_ASSERT(aRequest);
1643 MOZ_ASSERT(aURI);
1645 *aURI = nullptr;
1647 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
1648 if (!channel) {
1649 return;
1652 nsCOMPtr<nsIURI> uri;
1653 nsresult rv = channel->GetURI(getter_AddRefs(uri));
1654 if (NS_FAILED(rv)) {
1655 return;
1658 if (!dom::IsBlobURI(uri)) {
1659 return;
1662 uri.forget(aURI);
1665 nsresult GetLocalFileFromChannel(nsIRequest* aRequest, nsIFile** aFile) {
1666 MOZ_ASSERT(aRequest);
1667 MOZ_ASSERT(aFile);
1669 *aFile = nullptr;
1671 nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aRequest);
1672 if (!fc) {
1673 return NS_OK;
1676 nsCOMPtr<nsIFile> file;
1677 nsresult rv = fc->GetFile(getter_AddRefs(file));
1678 if (NS_WARN_IF(NS_FAILED(rv))) {
1679 return rv;
1682 file.forget(aFile);
1683 return NS_OK;
1686 nsresult DummyStreamReaderFunc(nsIInputStream* aInputStream, void* aClosure,
1687 const char* aFromRawSegment, uint32_t aToOffset,
1688 uint32_t aCount, uint32_t* aWriteCount) {
1689 *aWriteCount = aCount;
1690 return NS_OK;
1693 class FileCreationHandler final : public PromiseNativeHandler {
1694 public:
1695 NS_DECL_ISUPPORTS
1697 static void Create(Promise* aPromise, XMLHttpRequestMainThread* aXHR) {
1698 MOZ_ASSERT(aPromise);
1700 RefPtr<FileCreationHandler> handler = new FileCreationHandler(aXHR);
1701 aPromise->AppendNativeHandler(handler);
1704 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
1705 if (NS_WARN_IF(!aValue.isObject())) {
1706 mXHR->LocalFileToBlobCompleted(nullptr);
1707 return;
1710 RefPtr<Blob> blob;
1711 if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) {
1712 mXHR->LocalFileToBlobCompleted(nullptr);
1713 return;
1716 mXHR->LocalFileToBlobCompleted(blob->Impl());
1719 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
1720 mXHR->LocalFileToBlobCompleted(nullptr);
1723 private:
1724 explicit FileCreationHandler(XMLHttpRequestMainThread* aXHR) : mXHR(aXHR) {
1725 MOZ_ASSERT(aXHR);
1728 ~FileCreationHandler() = default;
1730 RefPtr<XMLHttpRequestMainThread> mXHR;
1733 NS_IMPL_ISUPPORTS0(FileCreationHandler)
1735 } // namespace
1737 void XMLHttpRequestMainThread::LocalFileToBlobCompleted(BlobImpl* aBlobImpl) {
1738 MOZ_ASSERT(mState != XMLHttpRequest_Binding::DONE);
1740 mResponseBlobImpl = aBlobImpl;
1741 mBlobStorage = nullptr;
1742 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1744 ChangeStateToDone(mFlagSyncLooping);
1747 NS_IMETHODIMP
1748 XMLHttpRequestMainThread::OnDataAvailable(nsIRequest* request,
1749 nsIInputStream* inStr,
1750 uint64_t sourceOffset,
1751 uint32_t count) {
1752 NS_ENSURE_ARG_POINTER(inStr);
1754 mProgressSinceLastProgressEvent = true;
1755 XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
1757 nsresult rv;
1759 if (mResponseType == XMLHttpRequestResponseType::Blob) {
1760 nsCOMPtr<nsIFile> localFile;
1761 nsCOMPtr<nsIURI> blobURI;
1762 GetBlobURIFromChannel(request, getter_AddRefs(blobURI));
1763 if (blobURI) {
1764 RefPtr<BlobImpl> blobImpl;
1765 rv = NS_GetBlobForBlobURI(blobURI, getter_AddRefs(blobImpl));
1766 if (NS_SUCCEEDED(rv)) {
1767 mResponseBlobImpl = blobImpl;
1769 } else {
1770 rv = GetLocalFileFromChannel(request, getter_AddRefs(localFile));
1772 if (NS_WARN_IF(NS_FAILED(rv))) {
1773 return rv;
1776 if (mResponseBlobImpl || localFile) {
1777 mBlobStorage = nullptr;
1778 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1780 // The nsIStreamListener contract mandates us to read from the stream
1781 // before returning.
1782 uint32_t totalRead;
1783 rv = inStr->ReadSegments(DummyStreamReaderFunc, nullptr, count,
1784 &totalRead);
1785 NS_ENSURE_SUCCESS(rv, rv);
1787 ChangeState(XMLHttpRequest_Binding::LOADING);
1789 // Cancel() must be called with an error. We use
1790 // NS_ERROR_FILE_ALREADY_EXISTS to know that we've aborted the operation
1791 // just because we can retrieve the File from the channel directly.
1792 return request->Cancel(NS_ERROR_FILE_ALREADY_EXISTS);
1796 uint32_t totalRead;
1797 rv = inStr->ReadSegments(XMLHttpRequestMainThread::StreamReaderFunc,
1798 (void*)this, count, &totalRead);
1799 NS_ENSURE_SUCCESS(rv, rv);
1801 // Fire the first progress event/loading state change
1802 if (mState == XMLHttpRequest_Binding::HEADERS_RECEIVED) {
1803 ChangeState(XMLHttpRequest_Binding::LOADING);
1804 if (!mFlagSynchronous) {
1805 DispatchProgressEvent(this, ProgressEventType::progress, mLoadTransferred,
1806 mLoadTotal);
1808 mProgressSinceLastProgressEvent = false;
1811 if (!mFlagSynchronous && !mProgressTimerIsActive) {
1812 StartProgressEventTimer();
1815 return NS_OK;
1818 NS_IMETHODIMP
1819 XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) {
1820 AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStartRequest", NETWORK);
1822 nsresult rv = NS_OK;
1823 if (!mFirstStartRequestSeen && mRequestObserver) {
1824 mFirstStartRequestSeen = true;
1825 mRequestObserver->OnStartRequest(request);
1828 if (request != mChannel) {
1829 // Can this still happen?
1830 return NS_OK;
1833 // Don't do anything if we have been aborted
1834 if (mState == XMLHttpRequest_Binding::UNSENT) {
1835 return NS_OK;
1838 // Don't do anything if we're in mid-abort, but let the request
1839 // know (this can happen due to race conditions in valid XHRs,
1840 // see bz1070763 for info).
1841 if (mFlagAborted) {
1842 return NS_BINDING_ABORTED;
1845 // Don't do anything if we have timed out.
1846 if (mFlagTimedOut) {
1847 return NS_OK;
1850 nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
1851 NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
1853 nsresult status;
1854 request->GetStatus(&status);
1855 if (mErrorLoad == ErrorType::eOK && NS_FAILED(status)) {
1856 mErrorLoad = ErrorType::eRequest;
1859 // Upload phase is now over. If we were uploading anything,
1860 // stop the timer and fire any final progress events.
1861 if (mUpload && !mUploadComplete && mErrorLoad == ErrorType::eOK &&
1862 !mFlagSynchronous) {
1863 StopProgressEventTimer();
1865 mUploadTransferred = mUploadTotal;
1867 if (mProgressSinceLastProgressEvent) {
1868 DispatchProgressEvent(mUpload, ProgressEventType::progress,
1869 mUploadTransferred, mUploadTotal);
1870 mProgressSinceLastProgressEvent = false;
1873 mUploadComplete = true;
1874 DispatchProgressEvent(mUpload, ProgressEventType::load, mUploadTotal,
1875 mUploadTotal);
1878 mFlagParseBody = true;
1879 if (mErrorLoad == ErrorType::eOK) {
1880 ChangeState(XMLHttpRequest_Binding::HEADERS_RECEIVED);
1883 ResetResponse();
1885 if (!mOverrideMimeType.IsEmpty()) {
1886 channel->SetContentType(NS_ConvertUTF16toUTF8(mOverrideMimeType));
1889 // Fallback to 'application/octet-stream'
1890 nsAutoCString type;
1891 channel->GetContentType(type);
1892 if (type.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
1893 channel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
1896 DetectCharset();
1898 // Set up arraybuffer
1899 if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
1900 NS_SUCCEEDED(status)) {
1901 if (mIsMappedArrayBuffer) {
1902 nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
1903 if (jarChannel) {
1904 nsCOMPtr<nsIURI> uri;
1905 rv = channel->GetURI(getter_AddRefs(uri));
1906 if (NS_SUCCEEDED(rv)) {
1907 nsAutoCString file;
1908 nsAutoCString scheme;
1909 uri->GetScheme(scheme);
1910 if (scheme.LowerCaseEqualsLiteral("jar")) {
1911 nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri);
1912 if (jarURI) {
1913 jarURI->GetJAREntry(file);
1916 nsCOMPtr<nsIFile> jarFile;
1917 jarChannel->GetJarFile(getter_AddRefs(jarFile));
1918 if (!jarFile) {
1919 mIsMappedArrayBuffer = false;
1920 } else {
1921 rv = mArrayBufferBuilder->MapToFileInPackage(file, jarFile);
1922 // This can happen legitimately if there are compressed files
1923 // in the jarFile. See bug #1357219. No need to warn on the error.
1924 if (NS_FAILED(rv)) {
1925 mIsMappedArrayBuffer = false;
1926 } else {
1927 channel->SetContentType("application/mem-mapped"_ns);
1933 // If memory mapping failed, mIsMappedArrayBuffer would be set to false,
1934 // and we want it fallback to the malloc way.
1935 if (!mIsMappedArrayBuffer) {
1936 int64_t contentLength;
1937 rv = channel->GetContentLength(&contentLength);
1938 if (NS_SUCCEEDED(rv) && contentLength > 0 &&
1939 contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) {
1940 mArrayBufferBuilder->SetCapacity(static_cast<int32_t>(contentLength));
1945 // Set up responseXML
1946 // Note: Main Fetch step 18 requires to ignore body for head/connect methods.
1947 bool parseBody = (mResponseType == XMLHttpRequestResponseType::_empty ||
1948 mResponseType == XMLHttpRequestResponseType::Document) &&
1949 !(mRequestMethod.EqualsLiteral("HEAD") ||
1950 mRequestMethod.EqualsLiteral("CONNECT"));
1952 if (parseBody) {
1953 // Do not try to parse documents if content-length = 0
1954 int64_t contentLength;
1955 if (NS_SUCCEEDED(mChannel->GetContentLength(&contentLength)) &&
1956 contentLength == 0) {
1957 parseBody = false;
1961 mIsHtml = false;
1962 mWarnAboutSyncHtml = false;
1963 if (parseBody && NS_SUCCEEDED(status)) {
1964 // We can gain a huge performance win by not even trying to
1965 // parse non-XML data. This also protects us from the situation
1966 // where we have an XML document and sink, but HTML (or other)
1967 // parser, which can produce unreliable results.
1968 nsAutoCString type;
1969 channel->GetContentType(type);
1971 if ((mResponseType == XMLHttpRequestResponseType::Document) &&
1972 type.EqualsLiteral("text/html")) {
1973 // HTML parsing is only supported for responseType == "document" to
1974 // avoid running the parser and, worse, populating responseXML for
1975 // legacy users of XHR who use responseType == "" for retrieving the
1976 // responseText of text/html resources. This legacy case is so common
1977 // that it's not useful to emit a warning about it.
1978 if (mFlagSynchronous) {
1979 // We don't make cool new features available in the bad synchronous
1980 // mode. The synchronous mode is for legacy only.
1981 mWarnAboutSyncHtml = true;
1982 mFlagParseBody = false;
1983 } else {
1984 mIsHtml = true;
1986 } else if (!(type.EqualsLiteral("text/xml") ||
1987 type.EqualsLiteral("application/xml") ||
1988 type.RFind("+xml", true, -1, 4) != kNotFound)) {
1989 // Follow https://xhr.spec.whatwg.org/
1990 // If final MIME type is not null, text/html, text/xml, application/xml,
1991 // or does not end in +xml, return null.
1992 mFlagParseBody = false;
1994 } else {
1995 // The request failed, so we shouldn't be parsing anyway
1996 mFlagParseBody = false;
1999 if (mFlagParseBody) {
2000 nsCOMPtr<nsIURI> baseURI, docURI;
2001 rv = mChannel->GetURI(getter_AddRefs(docURI));
2002 NS_ENSURE_SUCCESS(rv, rv);
2003 baseURI = docURI;
2005 nsCOMPtr<Document> doc = GetDocumentIfCurrent();
2006 nsCOMPtr<nsIURI> chromeXHRDocURI, chromeXHRDocBaseURI;
2007 if (doc) {
2008 chromeXHRDocURI = doc->GetDocumentURI();
2009 chromeXHRDocBaseURI = doc->GetBaseURI();
2010 } else {
2011 // If we're no longer current, just kill the load, though it really should
2012 // have been killed already.
2013 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
2014 return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
2018 // Create an empty document from it.
2019 const auto& emptyStr = u""_ns;
2020 nsIGlobalObject* global = DOMEventTargetHelper::GetParentObject();
2022 nsCOMPtr<nsIPrincipal> requestingPrincipal;
2023 rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
2024 channel, getter_AddRefs(requestingPrincipal));
2025 NS_ENSURE_SUCCESS(rv, rv);
2027 rv = NS_NewDOMDocument(
2028 getter_AddRefs(mResponseXML), emptyStr, emptyStr, nullptr, docURI,
2029 baseURI, requestingPrincipal, true, global,
2030 mIsHtml ? DocumentFlavorHTML : DocumentFlavorLegacyGuess);
2031 NS_ENSURE_SUCCESS(rv, rv);
2032 mResponseXML->SetChromeXHRDocURI(chromeXHRDocURI);
2033 mResponseXML->SetChromeXHRDocBaseURI(chromeXHRDocBaseURI);
2035 // suppress parsing failure messages to console for statuses which
2036 // can have empty bodies (see bug 884693).
2037 IgnoredErrorResult rv2;
2038 uint32_t responseStatus = GetStatus(rv2);
2039 if (!rv2.Failed() && (responseStatus == 201 || responseStatus == 202 ||
2040 responseStatus == 204 || responseStatus == 205 ||
2041 responseStatus == 304)) {
2042 mResponseXML->SetSuppressParserErrorConsoleMessages(true);
2045 if (mPrincipal->IsSystemPrincipal()) {
2046 mResponseXML->ForceEnableXULXBL();
2049 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2050 bool isCrossSite = false;
2051 isCrossSite = loadInfo->GetTainting() != LoadTainting::Basic;
2053 if (isCrossSite) {
2054 mResponseXML->DisableCookieAccess();
2057 nsCOMPtr<nsIStreamListener> listener;
2058 nsCOMPtr<nsILoadGroup> loadGroup;
2059 channel->GetLoadGroup(getter_AddRefs(loadGroup));
2061 // suppress <parsererror> nodes on XML document parse failure, but only
2062 // for non-privileged code (including Web Extensions). See bug 289714.
2063 if (!IsSystemXHR()) {
2064 mResponseXML->SetSuppressParserErrorElement(true);
2067 rv = mResponseXML->StartDocumentLoad(kLoadAsData, channel, loadGroup,
2068 nullptr, getter_AddRefs(listener),
2069 !isCrossSite);
2070 NS_ENSURE_SUCCESS(rv, rv);
2072 // the spec requires the response document.referrer to be the empty string
2073 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2074 new ReferrerInfo(nullptr, mResponseXML->ReferrerPolicy());
2075 mResponseXML->SetReferrerInfo(referrerInfo);
2077 mXMLParserStreamListener = listener;
2078 rv = mXMLParserStreamListener->OnStartRequest(request);
2079 NS_ENSURE_SUCCESS(rv, rv);
2082 // Download phase beginning; start the progress event timer if necessary.
2083 if (NS_SUCCEEDED(rv) && HasListenersFor(nsGkAtoms::onprogress)) {
2084 StartProgressEventTimer();
2087 return NS_OK;
2090 NS_IMETHODIMP
2091 XMLHttpRequestMainThread::OnStopRequest(nsIRequest* request, nsresult status) {
2092 AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStopRequest", NETWORK);
2094 if (request != mChannel) {
2095 // Can this still happen?
2096 return NS_OK;
2099 // Send the decoder the signal that we've hit the end of the stream,
2100 // but only when decoding text eagerly.
2101 if (mDecoder && ((mResponseType == XMLHttpRequestResponseType::Text) ||
2102 (mResponseType == XMLHttpRequestResponseType::Json) ||
2103 (mResponseType == XMLHttpRequestResponseType::_empty &&
2104 !mResponseXML))) {
2105 AppendToResponseText(Span<const uint8_t>(), true);
2108 mWaitingForOnStopRequest = false;
2110 if (mRequestObserver) {
2111 NS_ASSERTION(mFirstStartRequestSeen, "Inconsistent state!");
2112 mFirstStartRequestSeen = false;
2113 mRequestObserver->OnStopRequest(request, status);
2116 // make sure to notify the listener if we were aborted
2117 // XXX in fact, why don't we do the cleanup below in this case??
2118 // UNSENT is for abort calls. See OnStartRequest above.
2119 if (mState == XMLHttpRequest_Binding::UNSENT || mFlagTimedOut) {
2120 if (mXMLParserStreamListener)
2121 (void)mXMLParserStreamListener->OnStopRequest(request, status);
2122 return NS_OK;
2125 // Is this good enough here?
2126 if (mXMLParserStreamListener && mFlagParseBody) {
2127 mXMLParserStreamListener->OnStopRequest(request, status);
2130 mXMLParserStreamListener = nullptr;
2131 mContext = nullptr;
2133 // If window.stop() or other aborts were issued, handle as an abort
2134 if (status == NS_BINDING_ABORTED) {
2135 mFlagParseBody = false;
2136 IgnoredErrorResult rv;
2137 RequestErrorSteps(ProgressEventType::abort, NS_OK, rv);
2138 ChangeState(XMLHttpRequest_Binding::UNSENT, false);
2139 return NS_OK;
2142 // If we were just reading a blob URL, we're already done
2143 if (status == NS_ERROR_FILE_ALREADY_EXISTS && mResponseBlobImpl) {
2144 ChangeStateToDone(mFlagSyncLooping);
2145 return NS_OK;
2148 bool waitingForBlobCreation = false;
2150 // If we have this error, we have to deal with a file: URL + responseType =
2151 // blob. We have this error because we canceled the channel. The status will
2152 // be set to NS_OK.
2153 if (!mResponseBlobImpl && status == NS_ERROR_FILE_ALREADY_EXISTS &&
2154 mResponseType == XMLHttpRequestResponseType::Blob) {
2155 nsCOMPtr<nsIFile> file;
2156 nsresult rv = GetLocalFileFromChannel(request, getter_AddRefs(file));
2157 if (NS_WARN_IF(NS_FAILED(rv))) {
2158 return rv;
2161 if (file) {
2162 nsAutoCString contentType;
2163 rv = mChannel->GetContentType(contentType);
2164 if (NS_WARN_IF(NS_FAILED(rv))) {
2165 return rv;
2168 ChromeFilePropertyBag bag;
2169 CopyUTF8toUTF16(contentType, bag.mType);
2171 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
2173 ErrorResult error;
2174 RefPtr<Promise> promise =
2175 FileCreatorHelper::CreateFile(global, file, bag, true, error);
2176 if (NS_WARN_IF(error.Failed())) {
2177 return error.StealNSResult();
2180 FileCreationHandler::Create(promise, this);
2181 waitingForBlobCreation = true;
2182 status = NS_OK;
2184 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
2185 NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
2189 if (NS_SUCCEEDED(status) &&
2190 mResponseType == XMLHttpRequestResponseType::Blob &&
2191 !waitingForBlobCreation) {
2192 // Smaller files may be written in cache map instead of separate files.
2193 // Also, no-store response cannot be written in persistent cache.
2194 nsAutoCString contentType;
2195 if (!mOverrideMimeType.IsEmpty()) {
2196 contentType.Assign(NS_ConvertUTF16toUTF8(mOverrideMimeType));
2197 } else {
2198 mChannel->GetContentType(contentType);
2201 // mBlobStorage can be null if the channel is non-file non-cacheable
2202 // and if the response length is zero.
2203 MaybeCreateBlobStorage();
2204 mBlobStorage->GetBlobImplWhenReady(contentType, this);
2205 waitingForBlobCreation = true;
2207 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
2208 NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
2209 } else if (NS_SUCCEEDED(status) && !mIsMappedArrayBuffer &&
2210 mResponseType == XMLHttpRequestResponseType::Arraybuffer) {
2211 // set the capacity down to the actual length, to realloc back
2212 // down to the actual size
2213 if (!mArrayBufferBuilder->SetCapacity(mArrayBufferBuilder->Length())) {
2214 // this should never happen!
2215 status = NS_ERROR_UNEXPECTED;
2219 nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
2220 NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
2222 channel->SetNotificationCallbacks(nullptr);
2223 mNotificationCallbacks = nullptr;
2224 mChannelEventSink = nullptr;
2225 mProgressEventSink = nullptr;
2227 bool wasSync = mFlagSyncLooping;
2228 mFlagSyncLooping = false;
2229 mRequestSentTime = 0;
2231 // update our charset and decoder to match mResponseXML,
2232 // before it is possibly nulled out
2233 MatchCharsetAndDecoderToResponseDocument();
2235 if (NS_FAILED(status)) {
2236 // This can happen if the server is unreachable. Other possible
2237 // reasons are that the user leaves the page or hits the ESC key.
2239 mErrorLoad = ErrorType::eUnreachable;
2240 mResponseXML = nullptr;
2243 // If we're uninitialized at this point, we encountered an error
2244 // earlier and listeners have already been notified. Also we do
2245 // not want to do this if we already completed.
2246 if (mState == XMLHttpRequest_Binding::UNSENT ||
2247 mState == XMLHttpRequest_Binding::DONE) {
2248 return NS_OK;
2251 if (!mResponseXML) {
2252 mFlagParseBody = false;
2254 // We postpone the 'done' until the creation of the Blob is completed.
2255 if (!waitingForBlobCreation) {
2256 ChangeStateToDone(wasSync);
2259 return NS_OK;
2262 if (mIsHtml) {
2263 NS_ASSERTION(!mFlagSyncLooping,
2264 "We weren't supposed to support HTML parsing with XHR!");
2265 mParseEndListener = new nsXHRParseEndListener(this);
2266 RefPtr<EventTarget> eventTarget = mResponseXML;
2267 EventListenerManager* manager = eventTarget->GetOrCreateListenerManager();
2268 manager->AddEventListenerByType(mParseEndListener,
2269 kLiteralString_DOMContentLoaded,
2270 TrustedEventsAtSystemGroupBubble());
2271 return NS_OK;
2272 } else {
2273 mFlagParseBody = false;
2276 // We might have been sent non-XML data. If that was the case,
2277 // we should null out the document member. The idea in this
2278 // check here is that if there is no document element it is not
2279 // an XML document. We might need a fancier check...
2280 if (!mResponseXML->GetRootElement()) {
2281 mErrorParsingXML = true;
2282 mResponseXML = nullptr;
2284 ChangeStateToDone(wasSync);
2285 return NS_OK;
2288 void XMLHttpRequestMainThread::OnBodyParseEnd() {
2289 mFlagParseBody = false;
2290 mParseEndListener = nullptr;
2291 ChangeStateToDone(mFlagSyncLooping);
2294 void XMLHttpRequestMainThread::MatchCharsetAndDecoderToResponseDocument() {
2295 if (mResponseXML &&
2296 (!mDecoder ||
2297 mDecoder->Encoding() != mResponseXML->GetDocumentCharacterSet())) {
2298 TruncateResponseText();
2299 mResponseBodyDecodedPos = 0;
2300 mEofDecoded = false;
2301 mDecoder = mResponseXML->GetDocumentCharacterSet()->NewDecoder();
2304 void XMLHttpRequestMainThread::DisconnectDoneNotifier() {
2305 if (mDelayedDoneNotifier) {
2306 // Disconnect may release the last reference to 'this'.
2307 RefPtr<XMLHttpRequestMainThread> kungfuDeathGrip = this;
2308 mDelayedDoneNotifier->Disconnect();
2309 mDelayedDoneNotifier = nullptr;
2313 void XMLHttpRequestMainThread::ChangeStateToDone(bool aWasSync) {
2314 DisconnectDoneNotifier();
2316 if (!mForWorker && !aWasSync && mChannel) {
2317 // If the top level page is loading, try to postpone the handling of the
2318 // final events.
2319 nsLoadFlags loadFlags = 0;
2320 mChannel->GetLoadFlags(&loadFlags);
2321 if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
2322 nsPIDOMWindowInner* owner = GetOwner();
2323 BrowsingContext* bc = owner ? owner->GetBrowsingContext() : nullptr;
2324 bc = bc ? bc->Top() : nullptr;
2325 if (bc && bc->IsLoading()) {
2326 MOZ_ASSERT(!mDelayedDoneNotifier);
2327 RefPtr<XMLHttpRequestDoneNotifier> notifier =
2328 new XMLHttpRequestDoneNotifier(this);
2329 mDelayedDoneNotifier = notifier;
2330 bc->AddDeprioritizedLoadRunner(notifier);
2331 return;
2336 ChangeStateToDoneInternal();
2339 void XMLHttpRequestMainThread::ChangeStateToDoneInternal() {
2340 DisconnectDoneNotifier();
2341 StopProgressEventTimer();
2343 MOZ_ASSERT(!mFlagParseBody,
2344 "ChangeStateToDone() called before async HTML parsing is done.");
2346 mFlagSend = false;
2348 if (mTimeoutTimer) {
2349 mTimeoutTimer->Cancel();
2352 // Per spec, fire the last download progress event, if any,
2353 // before readystatechange=4/done. (Note that 0-sized responses
2354 // will have not sent a progress event yet, so one must be sent here).
2355 if (!mFlagSynchronous &&
2356 (!mLoadTransferred || mProgressSinceLastProgressEvent)) {
2357 DispatchProgressEvent(this, ProgressEventType::progress, mLoadTransferred,
2358 mLoadTotal);
2359 mProgressSinceLastProgressEvent = false;
2362 // Notify the document when an XHR request completes successfully.
2363 // This is used by the password manager as a hint to observe DOM mutations.
2364 // Call this prior to changing state to DONE to ensure we set up the
2365 // observer before mutations occur.
2366 if (mErrorLoad == ErrorType::eOK) {
2367 Document* doc = GetDocumentIfCurrent();
2368 if (doc) {
2369 doc->NotifyFetchOrXHRSuccess();
2373 // Per spec, fire readystatechange=4/done before final error events.
2374 ChangeState(XMLHttpRequest_Binding::DONE, true);
2376 // Per spec, if we failed in the upload phase, fire a final error
2377 // and loadend events for the upload after readystatechange=4/done.
2378 if (!mFlagSynchronous && mUpload && !mUploadComplete) {
2379 DispatchProgressEvent(mUpload, ProgressEventType::error, 0, -1);
2382 // Per spec, fire download's load/error and loadend events after
2383 // readystatechange=4/done (and of course all upload events).
2384 if (mErrorLoad != ErrorType::eOK) {
2385 DispatchProgressEvent(this, ProgressEventType::error, 0, -1);
2386 } else {
2387 DispatchProgressEvent(this, ProgressEventType::load, mLoadTransferred,
2388 mLoadTotal);
2391 if (mErrorLoad != ErrorType::eOK) {
2392 // By nulling out channel here we make it so that Send() can test
2393 // for that and throw. Also calling the various status
2394 // methods/members will not throw.
2395 // This matches what IE does.
2396 mChannel = nullptr;
2400 nsresult XMLHttpRequestMainThread::CreateChannel() {
2401 // When we are called from JS we can find the load group for the page,
2402 // and add ourselves to it. This way any pending requests
2403 // will be automatically aborted if the user leaves the page.
2404 nsCOMPtr<nsILoadGroup> loadGroup = GetLoadGroup();
2406 nsSecurityFlags secFlags;
2407 nsLoadFlags loadFlags = nsIRequest::LOAD_BACKGROUND;
2408 uint32_t sandboxFlags = 0;
2409 if (mPrincipal->IsSystemPrincipal()) {
2410 // When chrome is loading we want to make sure to sandbox any potential
2411 // result document. We also want to allow cross-origin loads.
2412 secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
2413 sandboxFlags = SANDBOXED_ORIGIN;
2414 } else if (IsSystemXHR()) {
2415 // For pages that have appropriate permissions, we want to still allow
2416 // cross-origin loads, but make sure that the any potential result
2417 // documents get the same principal as the loader.
2418 secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
2419 nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
2420 loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
2421 } else {
2422 // Otherwise use CORS. Again, make sure that potential result documents
2423 // use the same principal as the loader.
2424 secFlags = nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT |
2425 nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
2428 if (mIsAnon) {
2429 secFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
2432 // Use the responsibleDocument if we have it, except for dedicated workers
2433 // where it will be the parent document, which is not the one we want to use.
2434 nsresult rv;
2435 nsCOMPtr<Document> responsibleDocument = GetDocumentIfCurrent();
2436 if (responsibleDocument &&
2437 responsibleDocument->NodePrincipal() == mPrincipal) {
2438 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL,
2439 responsibleDocument, secFlags,
2440 nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2441 nullptr, // aPerformanceStorage
2442 loadGroup,
2443 nullptr, // aCallbacks
2444 loadFlags, nullptr, sandboxFlags);
2445 } else if (mClientInfo.isSome()) {
2446 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
2447 mClientInfo.ref(), mController, secFlags,
2448 nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2449 mCookieJarSettings,
2450 mPerformanceStorage, // aPerformanceStorage
2451 loadGroup,
2452 nullptr, // aCallbacks
2453 loadFlags, nullptr, sandboxFlags);
2454 } else {
2455 // Otherwise use the principal.
2456 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
2457 secFlags, nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2458 mCookieJarSettings,
2459 mPerformanceStorage, // aPerformanceStorage
2460 loadGroup,
2461 nullptr, // aCallbacks
2462 loadFlags, nullptr, sandboxFlags);
2464 NS_ENSURE_SUCCESS(rv, rv);
2466 if (mCSPEventListener) {
2467 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2468 rv = loadInfo->SetCspEventListener(mCSPEventListener);
2469 NS_ENSURE_SUCCESS(rv, rv);
2472 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2473 if (httpChannel) {
2474 rv = httpChannel->SetRequestMethod(mRequestMethod);
2475 NS_ENSURE_SUCCESS(rv, rv);
2477 httpChannel->SetSource(profiler_capture_backtrace());
2479 // Set the initiator type
2480 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
2481 if (timedChannel) {
2482 timedChannel->SetInitiatorType(u"xmlhttprequest"_ns);
2486 return NS_OK;
2489 void XMLHttpRequestMainThread::MaybeLowerChannelPriority() {
2490 nsCOMPtr<Document> doc = GetDocumentIfCurrent();
2491 if (!doc) {
2492 return;
2495 AutoJSAPI jsapi;
2496 if (!jsapi.Init(GetOwnerGlobal())) {
2497 return;
2500 JSContext* cx = jsapi.cx();
2502 if (!doc->IsScriptTracking(cx)) {
2503 return;
2506 if (StaticPrefs::network_http_tailing_enabled()) {
2507 nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel);
2508 if (cos) {
2509 // Adding TailAllowed to overrule the Unblocked flag, but to preserve
2510 // the effect of Unblocked when tailing is off.
2511 cos->AddClassFlags(nsIClassOfService::Throttleable |
2512 nsIClassOfService::Tail |
2513 nsIClassOfService::TailAllowed);
2517 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
2518 if (p) {
2519 p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
2523 nsresult XMLHttpRequestMainThread::InitiateFetch(
2524 already_AddRefed<nsIInputStream> aUploadStream, int64_t aUploadLength,
2525 nsACString& aUploadContentType) {
2526 nsresult rv;
2527 nsCOMPtr<nsIInputStream> uploadStream = std::move(aUploadStream);
2529 if (!uploadStream) {
2530 RefPtr<PreloaderBase> preload = FindPreload();
2531 if (preload) {
2532 // Because of bug 682305, we can't let listener be the XHR object itself
2533 // because JS wouldn't be able to use it. So create a listener around
2534 // 'this'. Make sure to hold a strong reference so that we don't leak the
2535 // wrapper.
2536 nsCOMPtr<nsIStreamListener> listener =
2537 new net::nsStreamListenerWrapper(this);
2538 rv = preload->AsyncConsume(listener);
2539 if (NS_SUCCEEDED(rv)) {
2540 mFromPreload = true;
2542 // May be null when the preload has already finished, but the XHR code
2543 // is safe to live with it.
2544 mChannel = preload->Channel();
2545 MOZ_ASSERT(mChannel);
2546 EnsureChannelContentType();
2547 return NS_OK;
2550 preload = nullptr;
2554 // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which
2555 // in turn keeps STOP button from becoming active. If the consumer passed in
2556 // a progress event handler we must load with nsIRequest::LOAD_NORMAL or
2557 // necko won't generate any progress notifications.
2558 if (HasListenersFor(nsGkAtoms::onprogress) ||
2559 (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress))) {
2560 nsLoadFlags loadFlags;
2561 mChannel->GetLoadFlags(&loadFlags);
2562 loadFlags &= ~nsIRequest::LOAD_BACKGROUND;
2563 loadFlags |= nsIRequest::LOAD_NORMAL;
2564 mChannel->SetLoadFlags(loadFlags);
2567 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2568 if (httpChannel) {
2569 // If the user hasn't overridden the Accept header, set it to */* per spec.
2570 if (!mAuthorRequestHeaders.Has("accept")) {
2571 mAuthorRequestHeaders.Set("accept", "*/*"_ns);
2574 mAuthorRequestHeaders.ApplyToChannel(httpChannel, false);
2576 if (!IsSystemXHR()) {
2577 nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
2578 nsCOMPtr<Document> doc = owner ? owner->GetExtantDoc() : nullptr;
2579 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2580 ReferrerInfo::CreateForFetch(mPrincipal, doc);
2581 Unused << httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
2584 // Some extensions override the http protocol handler and provide their own
2585 // implementation. The channels returned from that implementation don't
2586 // always seem to implement the nsIUploadChannel2 interface, presumably
2587 // because it's a new interface. Eventually we should remove this and simply
2588 // require that http channels implement the new interface (see bug 529041).
2589 nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
2590 if (!uploadChannel2) {
2591 nsCOMPtr<nsIConsoleService> consoleService =
2592 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
2593 if (consoleService) {
2594 consoleService->LogStringMessage(
2595 u"Http channel implementation doesn't support nsIUploadChannel2. "
2596 "An extension has supplied a non-functional http protocol handler. "
2597 "This will break behavior and in future releases not work at all.");
2601 if (uploadStream) {
2602 // If necessary, wrap the stream in a buffered stream so as to guarantee
2603 // support for our upload when calling ExplicitSetUploadStream.
2604 if (!NS_InputStreamIsBuffered(uploadStream)) {
2605 nsCOMPtr<nsIInputStream> bufferedStream;
2606 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
2607 uploadStream.forget(), 4096);
2608 NS_ENSURE_SUCCESS(rv, rv);
2610 uploadStream = bufferedStream;
2613 // We want to use a newer version of the upload channel that won't
2614 // ignore the necessary headers for an empty Content-Type.
2615 nsCOMPtr<nsIUploadChannel2> uploadChannel2(
2616 do_QueryInterface(httpChannel));
2617 // This assertion will fire if buggy extensions are installed
2618 NS_ASSERTION(uploadChannel2, "http must support nsIUploadChannel2");
2619 if (uploadChannel2) {
2620 uploadChannel2->ExplicitSetUploadStream(
2621 uploadStream, aUploadContentType, mUploadTotal, mRequestMethod,
2622 false);
2623 } else {
2624 // The http channel doesn't support the new nsIUploadChannel2.
2625 // Emulate it as best we can using nsIUploadChannel.
2626 if (aUploadContentType.IsEmpty()) {
2627 aUploadContentType.AssignLiteral("application/octet-stream");
2629 nsCOMPtr<nsIUploadChannel> uploadChannel =
2630 do_QueryInterface(httpChannel);
2631 uploadChannel->SetUploadStream(uploadStream, aUploadContentType,
2632 mUploadTotal);
2633 // Reset the method to its original value
2634 rv = httpChannel->SetRequestMethod(mRequestMethod);
2635 MOZ_ASSERT(NS_SUCCEEDED(rv));
2640 // Due to the chrome-only XHR.channel API, we need a hacky way to set the
2641 // SEC_COOKIES_INCLUDE *after* the channel has been has been created, since
2642 // .withCredentials can be called after open() is called.
2643 // Not doing this for privileged system XHRs since those don't use CORS.
2644 if (!IsSystemXHR() && !mIsAnon && mFlagACwithCredentials) {
2645 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2646 static_cast<net::LoadInfo*>(loadInfo.get())->SetIncludeCookiesSecFlag();
2649 // We never let XHR be blocked by head CSS/JS loads to avoid potential
2650 // deadlock where server generation of CSS/JS requires an XHR signal.
2651 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
2652 if (cos) {
2653 cos->AddClassFlags(nsIClassOfService::Unblocked);
2655 // Mark channel as urgent-start if the XHR is triggered by user input
2656 // events.
2657 if (UserActivation::IsHandlingUserInput()) {
2658 cos->AddClassFlags(nsIClassOfService::UrgentStart);
2662 // Disable Necko-internal response timeouts.
2663 nsCOMPtr<nsIHttpChannelInternal> internalHttpChannel(
2664 do_QueryInterface(mChannel));
2665 if (internalHttpChannel) {
2666 rv = internalHttpChannel->SetResponseTimeoutEnabled(false);
2667 MOZ_ASSERT(NS_SUCCEEDED(rv));
2670 if (!mIsAnon) {
2671 AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS);
2674 // Bypass the network cache in cases where it makes no sense:
2675 // POST responses are always unique, and we provide no API that would
2676 // allow our consumers to specify a "cache key" to access old POST
2677 // responses, so they are not worth caching.
2678 if (mRequestMethod.EqualsLiteral("POST")) {
2679 AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE |
2680 nsIRequest::INHIBIT_CACHING);
2681 } else {
2682 // When we are sync loading, we need to bypass the local cache when it would
2683 // otherwise block us waiting for exclusive access to the cache. If we
2684 // don't do this, then we could dead lock in some cases (see bug 309424).
2686 // Also don't block on the cache entry on async if it is busy - favoring
2687 // parallelism over cache hit rate for xhr. This does not disable the cache
2688 // everywhere - only in cases where more than one channel for the same URI
2689 // is accessed simultanously.
2690 AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
2693 EnsureChannelContentType();
2695 // Set up the preflight if needed
2696 if (!IsSystemXHR()) {
2697 nsTArray<nsCString> CORSUnsafeHeaders;
2698 mAuthorRequestHeaders.GetCORSUnsafeHeaders(CORSUnsafeHeaders);
2699 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2700 loadInfo->SetCorsPreflightInfo(CORSUnsafeHeaders,
2701 mFlagHadUploadListenersOnSend);
2704 // Hook us up to listen to redirects and the like. Only do this very late
2705 // since this creates a cycle between the channel and us. This cycle has
2706 // to be manually broken if anything below fails.
2707 mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
2708 mChannel->SetNotificationCallbacks(this);
2710 if (internalHttpChannel) {
2711 internalHttpChannel->SetBlockAuthPrompt(ShouldBlockAuthPrompt());
2714 // Because of bug 682305, we can't let listener be the XHR object itself
2715 // because JS wouldn't be able to use it. So create a listener around 'this'.
2716 // Make sure to hold a strong reference so that we don't leak the wrapper.
2717 nsCOMPtr<nsIStreamListener> listener = new net::nsStreamListenerWrapper(this);
2719 // Check if this XHR is created from a tracking script.
2720 // If yes, lower the channel's priority.
2721 if (StaticPrefs::privacy_trackingprotection_lower_network_priority()) {
2722 MaybeLowerChannelPriority();
2725 // Associate any originating stack with the channel.
2726 NotifyNetworkMonitorAlternateStack(mChannel, std::move(mOriginStack));
2728 // Start reading from the channel
2729 rv = mChannel->AsyncOpen(listener);
2730 listener = nullptr;
2731 if (NS_WARN_IF(NS_FAILED(rv))) {
2732 // Drop our ref to the channel to avoid cycles. Also drop channel's
2733 // ref to us to be extra safe.
2734 mChannel->SetNotificationCallbacks(mNotificationCallbacks);
2735 mChannel = nullptr;
2737 mErrorLoad = ErrorType::eChannelOpen;
2739 // Per spec, we throw on sync errors, but not async.
2740 if (mFlagSynchronous) {
2741 mState = XMLHttpRequest_Binding::DONE;
2742 return NS_ERROR_DOM_NETWORK_ERR;
2746 return NS_OK;
2749 already_AddRefed<PreloaderBase> XMLHttpRequestMainThread::FindPreload() {
2750 Document* doc = GetDocumentIfCurrent();
2751 if (!doc) {
2752 return nullptr;
2754 if (mPrincipal->IsSystemPrincipal() || IsSystemXHR()) {
2755 return nullptr;
2757 if (!mRequestMethod.EqualsLiteral("GET")) {
2758 // Preload can only do GET.
2759 return nullptr;
2761 if (!mAuthorRequestHeaders.IsEmpty()) {
2762 // Preload can't set headers.
2763 return nullptr;
2766 // mIsAnon overrules mFlagACwithCredentials.
2767 CORSMode cors = (mIsAnon || !mFlagACwithCredentials)
2768 ? CORSMode::CORS_ANONYMOUS
2769 : CORSMode::CORS_USE_CREDENTIALS;
2770 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2771 ReferrerInfo::CreateForFetch(mPrincipal, doc);
2772 auto key = PreloadHashKey::CreateAsFetch(mRequestURL, cors);
2773 RefPtr<PreloaderBase> preload = doc->Preloads().LookupPreload(key);
2774 if (!preload) {
2775 return nullptr;
2778 preload->RemoveSelf(doc);
2779 preload->NotifyUsage(PreloaderBase::LoadBackground::Keep);
2781 return preload.forget();
2784 void XMLHttpRequestMainThread::EnsureChannelContentType() {
2785 MOZ_ASSERT(mChannel);
2787 // Since we expect XML data, set the type hint accordingly
2788 // if the channel doesn't know any content type.
2789 // This means that we always try to parse local files as XML
2790 // ignoring return value, as this is not critical. Use text/xml as fallback
2791 // MIME type.
2792 nsAutoCString contentType;
2793 if (NS_FAILED(mChannel->GetContentType(contentType)) ||
2794 contentType.IsEmpty() ||
2795 contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
2796 mChannel->SetContentType("text/xml"_ns);
2800 void XMLHttpRequestMainThread::UnsuppressEventHandlingAndResume() {
2801 MOZ_ASSERT(NS_IsMainThread());
2802 MOZ_ASSERT(mFlagSynchronous);
2804 if (mSuspendedDoc) {
2805 mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(true);
2806 mSuspendedDoc = nullptr;
2809 if (mResumeTimeoutRunnable) {
2810 DispatchToMainThread(mResumeTimeoutRunnable.forget());
2811 mResumeTimeoutRunnable = nullptr;
2815 void XMLHttpRequestMainThread::Send(
2816 const Nullable<
2817 DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString>&
2818 aData,
2819 ErrorResult& aRv) {
2820 NOT_CALLABLE_IN_SYNC_SEND_RV
2822 if (aData.IsNull()) {
2823 SendInternal(nullptr, false, aRv);
2824 return;
2827 if (aData.Value().IsDocument()) {
2828 BodyExtractor<Document> body(&aData.Value().GetAsDocument());
2829 SendInternal(&body, true, aRv);
2830 return;
2833 if (aData.Value().IsBlob()) {
2834 BodyExtractor<const Blob> body(&aData.Value().GetAsBlob());
2835 SendInternal(&body, false, aRv);
2836 return;
2839 if (aData.Value().IsArrayBuffer()) {
2840 BodyExtractor<const ArrayBuffer> body(&aData.Value().GetAsArrayBuffer());
2841 SendInternal(&body, false, aRv);
2842 return;
2845 if (aData.Value().IsArrayBufferView()) {
2846 BodyExtractor<const ArrayBufferView> body(
2847 &aData.Value().GetAsArrayBufferView());
2848 SendInternal(&body, false, aRv);
2849 return;
2852 if (aData.Value().IsFormData()) {
2853 BodyExtractor<const FormData> body(&aData.Value().GetAsFormData());
2854 SendInternal(&body, false, aRv);
2855 return;
2858 if (aData.Value().IsURLSearchParams()) {
2859 BodyExtractor<const URLSearchParams> body(
2860 &aData.Value().GetAsURLSearchParams());
2861 SendInternal(&body, false, aRv);
2862 return;
2865 if (aData.Value().IsUSVString()) {
2866 BodyExtractor<const nsAString> body(&aData.Value().GetAsUSVString());
2867 SendInternal(&body, true, aRv);
2868 return;
2872 nsresult XMLHttpRequestMainThread::MaybeSilentSendFailure(nsresult aRv) {
2873 // Per spec, silently fail on async request failures; throw for sync.
2874 if (mFlagSynchronous) {
2875 mState = XMLHttpRequest_Binding::DONE;
2876 return NS_ERROR_DOM_NETWORK_ERR;
2879 // Defer the actual sending of async events just in case listeners
2880 // are attached after the send() method is called.
2881 Unused << NS_WARN_IF(
2882 NS_FAILED(DispatchToMainThread(NewRunnableMethod<ProgressEventType>(
2883 "dom::XMLHttpRequestMainThread::CloseRequestWithError", this,
2884 &XMLHttpRequestMainThread::CloseRequestWithError,
2885 ProgressEventType::error))));
2886 return NS_OK;
2889 void XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody,
2890 bool aBodyIsDocumentOrString,
2891 ErrorResult& aRv) {
2892 MOZ_ASSERT(NS_IsMainThread());
2894 if (!mPrincipal) {
2895 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
2896 return;
2899 // Step 1
2900 if (mState != XMLHttpRequest_Binding::OPENED) {
2901 aRv.ThrowInvalidStateError("XMLHttpRequest state must be OPENED.");
2902 return;
2905 // Step 2
2906 if (mFlagSend) {
2907 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
2908 return;
2911 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
2912 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
2913 return;
2916 // If open() failed to create the channel, then throw a network error
2917 // as per spec. We really should create the channel here in send(), but
2918 // we have internal code relying on the channel being created in open().
2919 if (!mChannel) {
2920 mFlagSend = true; // so CloseRequestWithError sets us to DONE.
2921 aRv = MaybeSilentSendFailure(NS_ERROR_DOM_NETWORK_ERR);
2922 return;
2925 // non-GET requests aren't allowed for blob.
2926 if (IsBlobURI(mRequestURL) && !mRequestMethod.EqualsLiteral("GET")) {
2927 mFlagSend = true; // so CloseRequestWithError sets us to DONE.
2928 aRv = MaybeSilentSendFailure(NS_ERROR_DOM_NETWORK_ERR);
2929 return;
2932 // XXX We should probably send a warning to the JS console
2933 // if there are no event listeners set and we are doing
2934 // an asynchronous call.
2936 mUploadTransferred = 0;
2937 mUploadTotal = 0;
2938 // By default we don't have any upload, so mark upload complete.
2939 mUploadComplete = true;
2940 mErrorLoad = ErrorType::eOK;
2941 mLoadTotal = -1;
2942 nsCOMPtr<nsIInputStream> uploadStream;
2943 nsAutoCString uploadContentType;
2944 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2945 if (aBody && httpChannel && !mRequestMethod.EqualsLiteral("GET") &&
2946 !mRequestMethod.EqualsLiteral("HEAD")) {
2947 nsAutoCString charset;
2948 nsAutoCString defaultContentType;
2949 uint64_t size_u64;
2950 aRv = aBody->GetAsStream(getter_AddRefs(uploadStream), &size_u64,
2951 defaultContentType, charset);
2952 if (aRv.Failed()) {
2953 return;
2956 // make sure it fits within js MAX_SAFE_INTEGER
2957 mUploadTotal =
2958 net::InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1;
2960 if (uploadStream) {
2961 // If author set no Content-Type, use the default from GetAsStream().
2962 mAuthorRequestHeaders.Get("content-type", uploadContentType);
2963 if (uploadContentType.IsVoid()) {
2964 uploadContentType = defaultContentType;
2965 } else if (aBodyIsDocumentOrString &&
2966 StaticPrefs::dom_xhr_standard_content_type_normalization()) {
2967 UniquePtr<CMimeType> contentTypeRecord =
2968 CMimeType::Parse(uploadContentType);
2969 nsAutoCString charset;
2970 if (contentTypeRecord &&
2971 contentTypeRecord->GetParameterValue(kLiteralString_charset,
2972 charset) &&
2973 !charset.EqualsIgnoreCase("utf-8")) {
2974 contentTypeRecord->SetParameterValue(kLiteralString_charset,
2975 kLiteralString_UTF_8);
2976 contentTypeRecord->Serialize(uploadContentType);
2978 } else if (!charset.IsEmpty()) {
2979 // We don't want to set a charset for streams.
2980 // Replace all case-insensitive matches of the charset in the
2981 // content-type with the correct case.
2982 RequestHeaders::CharsetIterator iter(uploadContentType);
2983 while (iter.Next()) {
2984 if (!iter.Equals(charset, nsCaseInsensitiveCStringComparator)) {
2985 iter.Replace(charset);
2990 mUploadComplete = false;
2994 ResetResponse();
2996 // Check if we should enable cross-origin upload listeners.
2997 if (mUpload && mUpload->HasListeners()) {
2998 mFlagHadUploadListenersOnSend = true;
3001 mIsMappedArrayBuffer = false;
3002 if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
3003 StaticPrefs::dom_mapped_arraybuffer_enabled()) {
3004 nsCOMPtr<nsIURI> uri;
3005 nsAutoCString scheme;
3007 aRv = mChannel->GetURI(getter_AddRefs(uri));
3008 if (!aRv.Failed()) {
3009 uri->GetScheme(scheme);
3010 if (scheme.LowerCaseEqualsLiteral("jar")) {
3011 mIsMappedArrayBuffer = true;
3016 aRv = InitiateFetch(uploadStream.forget(), mUploadTotal, uploadContentType);
3017 if (aRv.Failed()) {
3018 return;
3021 // Start our timeout
3022 mRequestSentTime = PR_Now();
3023 StartTimeoutTimer();
3025 mWaitingForOnStopRequest = true;
3027 // Step 8
3028 mFlagSend = true;
3030 // If we're synchronous, spin an event loop here and wait
3031 if (mFlagSynchronous) {
3032 mFlagSyncLooping = true;
3034 if (GetOwner()) {
3035 if (nsCOMPtr<nsPIDOMWindowOuter> topWindow =
3036 GetOwner()->GetOuterWindow()->GetInProcessTop()) {
3037 if (nsCOMPtr<nsPIDOMWindowInner> topInner =
3038 topWindow->GetCurrentInnerWindow()) {
3039 mSuspendedDoc = topWindow->GetExtantDoc();
3040 if (mSuspendedDoc) {
3041 mSuspendedDoc->SuppressEventHandling();
3043 topInner->Suspend();
3044 mResumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
3049 SuspendEventDispatching();
3050 StopProgressEventTimer();
3051 auto scopeExit = MakeScopeExit([&] {
3052 CancelSyncTimeoutTimer();
3053 UnsuppressEventHandlingAndResume();
3054 ResumeEventDispatching();
3057 SyncTimeoutType syncTimeoutType = MaybeStartSyncTimeoutTimer();
3058 if (syncTimeoutType == eErrorOrExpired) {
3059 Abort();
3060 aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
3061 return;
3064 nsAutoSyncOperation sync(mSuspendedDoc,
3065 SyncOperationBehavior::eSuspendInput);
3066 if (!SpinEventLoopUntil([&]() { return !mFlagSyncLooping; })) {
3067 aRv.Throw(NS_ERROR_UNEXPECTED);
3068 return;
3071 // Time expired... We should throw.
3072 if (syncTimeoutType == eTimerStarted && !mSyncTimeoutTimer) {
3073 aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
3074 return;
3076 } else {
3077 // Now that we've successfully opened the channel, we can change state. Note
3078 // that this needs to come after the AsyncOpen() and rv check, because this
3079 // can run script that would try to restart this request, and that could end
3080 // up doing our AsyncOpen on a null channel if the reentered AsyncOpen
3081 // fails.
3082 StopProgressEventTimer();
3084 // Upload phase beginning; start the progress event timer if necessary.
3085 if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) {
3086 StartProgressEventTimer();
3088 // Dispatch loadstart events
3089 DispatchProgressEvent(this, ProgressEventType::loadstart, 0, -1);
3090 if (mUpload && !mUploadComplete) {
3091 DispatchProgressEvent(mUpload, ProgressEventType::loadstart, 0,
3092 mUploadTotal);
3096 if (!mChannel) {
3097 aRv = MaybeSilentSendFailure(NS_ERROR_DOM_NETWORK_ERR);
3101 // http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader
3102 void XMLHttpRequestMainThread::SetRequestHeader(const nsACString& aName,
3103 const nsACString& aValue,
3104 ErrorResult& aRv) {
3105 NOT_CALLABLE_IN_SYNC_SEND_RV
3107 // Step 1
3108 if (mState != XMLHttpRequest_Binding::OPENED) {
3109 aRv.ThrowInvalidStateError("XMLHttpRequest state must be OPENED.");
3110 return;
3113 // Step 2
3114 if (mFlagSend) {
3115 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3116 return;
3119 // Step 3
3120 nsAutoCString value;
3121 NS_TrimHTTPWhitespace(aValue, value);
3123 // Step 4
3124 if (!NS_IsValidHTTPToken(aName) || !NS_IsReasonableHTTPHeaderValue(value)) {
3125 aRv.Throw(NS_ERROR_DOM_INVALID_HEADER_NAME);
3126 return;
3129 // Step 5
3130 bool isPrivilegedCaller = IsSystemXHR();
3131 bool isForbiddenHeader = nsContentUtils::IsForbiddenRequestHeader(aName);
3132 if (!isPrivilegedCaller && isForbiddenHeader) {
3133 AutoTArray<nsString, 1> params;
3134 CopyUTF8toUTF16(aName, *params.AppendElement());
3135 LogMessage("ForbiddenHeaderWarning", GetOwner(), params);
3136 return;
3139 // Step 6.1
3140 // Skipping for now, as normalizing the case of header names may not be
3141 // web-compatible. See bug 1285036.
3143 // Step 6.2-6.3
3144 // Gecko-specific: invalid headers can be set by privileged
3145 // callers, but will not merge.
3146 if (isPrivilegedCaller && isForbiddenHeader) {
3147 mAuthorRequestHeaders.Set(aName, value);
3148 } else {
3149 mAuthorRequestHeaders.MergeOrSet(aName, value);
3153 void XMLHttpRequestMainThread::SetTimeout(uint32_t aTimeout, ErrorResult& aRv) {
3154 NOT_CALLABLE_IN_SYNC_SEND_RV
3156 if (mFlagSynchronous && mState != XMLHttpRequest_Binding::UNSENT &&
3157 HasOrHasHadOwner()) {
3158 /* Timeout is not supported for synchronous requests with an owning window,
3159 per XHR2 spec. */
3160 LogMessage("TimeoutSyncXHRWarning", GetOwner());
3161 aRv.ThrowInvalidAccessError(
3162 "synchronous XMLHttpRequests do not support timeout and responseType");
3163 return;
3166 mTimeoutMilliseconds = aTimeout;
3167 if (mRequestSentTime) {
3168 StartTimeoutTimer();
3172 nsIEventTarget* XMLHttpRequestMainThread::GetTimerEventTarget() {
3173 if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
3174 return global->EventTargetFor(TaskCategory::Other);
3176 return nullptr;
3179 nsresult XMLHttpRequestMainThread::DispatchToMainThread(
3180 already_AddRefed<nsIRunnable> aRunnable) {
3181 if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
3182 nsCOMPtr<nsIEventTarget> target =
3183 global->EventTargetFor(TaskCategory::Other);
3184 MOZ_ASSERT(target);
3186 return target->Dispatch(std::move(aRunnable), NS_DISPATCH_NORMAL);
3189 return NS_DispatchToMainThread(std::move(aRunnable));
3192 void XMLHttpRequestMainThread::StartTimeoutTimer() {
3193 MOZ_ASSERT(
3194 mRequestSentTime,
3195 "StartTimeoutTimer mustn't be called before the request was sent!");
3196 if (mState == XMLHttpRequest_Binding::DONE) {
3197 // do nothing!
3198 return;
3201 if (mTimeoutTimer) {
3202 mTimeoutTimer->Cancel();
3205 if (!mTimeoutMilliseconds) {
3206 return;
3209 if (!mTimeoutTimer) {
3210 mTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
3212 uint32_t elapsed =
3213 (uint32_t)((PR_Now() - mRequestSentTime) / PR_USEC_PER_MSEC);
3214 mTimeoutTimer->InitWithCallback(
3215 this, mTimeoutMilliseconds > elapsed ? mTimeoutMilliseconds - elapsed : 0,
3216 nsITimer::TYPE_ONE_SHOT);
3219 uint16_t XMLHttpRequestMainThread::ReadyState() const { return mState; }
3221 void XMLHttpRequestMainThread::OverrideMimeType(const nsAString& aMimeType,
3222 ErrorResult& aRv) {
3223 NOT_CALLABLE_IN_SYNC_SEND_RV
3225 if (mState == XMLHttpRequest_Binding::LOADING ||
3226 mState == XMLHttpRequest_Binding::DONE) {
3227 aRv.ThrowInvalidStateError(
3228 "Cannot call 'overrideMimeType()' on XMLHttpRequest after 'send()' "
3229 "(when its state is LOADING or DONE).");
3230 return;
3233 UniquePtr<MimeType> parsed = MimeType::Parse(aMimeType);
3234 if (parsed) {
3235 parsed->Serialize(mOverrideMimeType);
3236 } else {
3237 mOverrideMimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
3241 bool XMLHttpRequestMainThread::MozBackgroundRequest() const {
3242 return mFlagBackgroundRequest;
3245 void XMLHttpRequestMainThread::SetMozBackgroundRequestExternal(
3246 bool aMozBackgroundRequest, ErrorResult& aRv) {
3247 if (!IsSystemXHR()) {
3248 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
3249 return;
3252 if (mState != XMLHttpRequest_Binding::UNSENT) {
3253 // Can't change this while we're in the middle of something.
3254 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3255 return;
3258 mFlagBackgroundRequest = aMozBackgroundRequest;
3261 void XMLHttpRequestMainThread::SetMozBackgroundRequest(
3262 bool aMozBackgroundRequest, ErrorResult& aRv) {
3263 // No errors for this webIDL method on main-thread.
3264 SetMozBackgroundRequestExternal(aMozBackgroundRequest, IgnoreErrors());
3267 void XMLHttpRequestMainThread::SetOriginStack(
3268 UniquePtr<SerializedStackHolder> aOriginStack) {
3269 mOriginStack = std::move(aOriginStack);
3272 void XMLHttpRequestMainThread::SetSource(
3273 UniquePtr<ProfileChunkedBuffer> aSource) {
3274 if (!mChannel) {
3275 return;
3277 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
3279 if (httpChannel) {
3280 httpChannel->SetSource(std::move(aSource));
3284 bool XMLHttpRequestMainThread::WithCredentials() const {
3285 return mFlagACwithCredentials;
3288 void XMLHttpRequestMainThread::SetWithCredentials(bool aWithCredentials,
3289 ErrorResult& aRv) {
3290 NOT_CALLABLE_IN_SYNC_SEND_RV
3292 // Return error if we're already processing a request. Note that we can't use
3293 // ReadyState() here, because it can't differentiate between "opened" and
3294 // "sent", so we use mState directly.
3296 if ((mState != XMLHttpRequest_Binding::UNSENT &&
3297 mState != XMLHttpRequest_Binding::OPENED) ||
3298 mFlagSend || mIsAnon) {
3299 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3300 return;
3303 mFlagACwithCredentials = aWithCredentials;
3306 nsresult XMLHttpRequestMainThread::ChangeState(uint16_t aState,
3307 bool aBroadcast) {
3308 mState = aState;
3309 nsresult rv = NS_OK;
3311 if (aState != XMLHttpRequest_Binding::HEADERS_RECEIVED &&
3312 aState != XMLHttpRequest_Binding::LOADING) {
3313 StopProgressEventTimer();
3316 if (aBroadcast &&
3317 (!mFlagSynchronous || aState == XMLHttpRequest_Binding::OPENED ||
3318 aState == XMLHttpRequest_Binding::DONE)) {
3319 rv = FireReadystatechangeEvent();
3322 return rv;
3325 /////////////////////////////////////////////////////
3326 // nsIChannelEventSink methods:
3328 NS_IMETHODIMP
3329 XMLHttpRequestMainThread::AsyncOnChannelRedirect(
3330 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
3331 nsIAsyncVerifyRedirectCallback* callback) {
3332 MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
3334 // Prepare to receive callback
3335 mRedirectCallback = callback;
3336 mNewRedirectChannel = aNewChannel;
3338 if (mChannelEventSink) {
3339 nsCOMPtr<nsIAsyncVerifyRedirectCallback> fwd = EnsureXPCOMifier();
3341 nsresult rv = mChannelEventSink->AsyncOnChannelRedirect(
3342 aOldChannel, aNewChannel, aFlags, fwd);
3343 if (NS_FAILED(rv)) {
3344 mRedirectCallback = nullptr;
3345 mNewRedirectChannel = nullptr;
3347 return rv;
3349 OnRedirectVerifyCallback(NS_OK);
3350 return NS_OK;
3353 nsresult XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result) {
3354 NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
3355 NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
3357 if (NS_SUCCEEDED(result)) {
3358 bool rewriteToGET = false;
3359 nsCOMPtr<nsIHttpChannel> oldHttpChannel = GetCurrentHttpChannel();
3360 // Fetch 4.4.11
3361 Unused << oldHttpChannel->ShouldStripRequestBodyHeader(mRequestMethod,
3362 &rewriteToGET);
3364 mChannel = mNewRedirectChannel;
3366 nsCOMPtr<nsIHttpChannel> newHttpChannel(do_QueryInterface(mChannel));
3367 if (newHttpChannel) {
3368 // Ensure all original headers are duplicated for the new channel (bug
3369 // #553888)
3370 mAuthorRequestHeaders.ApplyToChannel(newHttpChannel, rewriteToGET);
3372 } else {
3373 mErrorLoad = ErrorType::eRedirect;
3376 mNewRedirectChannel = nullptr;
3378 mRedirectCallback->OnRedirectVerifyCallback(result);
3379 mRedirectCallback = nullptr;
3381 // It's important that we return success here. If we return the result code
3382 // that we were passed, JavaScript callers who cancel the redirect will wind
3383 // up throwing an exception in the process.
3384 return NS_OK;
3387 /////////////////////////////////////////////////////
3388 // nsIProgressEventSink methods:
3391 NS_IMETHODIMP
3392 XMLHttpRequestMainThread::OnProgress(nsIRequest* aRequest, int64_t aProgress,
3393 int64_t aProgressMax) {
3394 // When uploading, OnProgress reports also headers in aProgress and
3395 // aProgressMax. So, try to remove the headers, if possible.
3396 bool lengthComputable = (aProgressMax != -1);
3397 if (InUploadPhase()) {
3398 int64_t loaded = aProgress;
3399 if (lengthComputable) {
3400 int64_t headerSize = aProgressMax - mUploadTotal;
3401 loaded -= headerSize;
3403 mUploadTransferred = loaded;
3404 mProgressSinceLastProgressEvent = true;
3406 if (!mFlagSynchronous && !mProgressTimerIsActive) {
3407 StartProgressEventTimer();
3409 } else {
3410 mLoadTotal = aProgressMax;
3411 mLoadTransferred = aProgress;
3412 // OnDataAvailable() handles mProgressSinceLastProgressEvent
3413 // for the download phase.
3416 if (mProgressEventSink) {
3417 mProgressEventSink->OnProgress(aRequest, aProgress, aProgressMax);
3420 return NS_OK;
3423 NS_IMETHODIMP
3424 XMLHttpRequestMainThread::OnStatus(nsIRequest* aRequest, nsresult aStatus,
3425 const char16_t* aStatusArg) {
3426 if (mProgressEventSink) {
3427 mProgressEventSink->OnStatus(aRequest, aStatus, aStatusArg);
3430 return NS_OK;
3433 bool XMLHttpRequestMainThread::AllowUploadProgress() {
3434 return !IsCrossSiteCORSRequest() || mFlagHadUploadListenersOnSend;
3437 /////////////////////////////////////////////////////
3438 // nsIInterfaceRequestor methods:
3440 NS_IMETHODIMP
3441 XMLHttpRequestMainThread::GetInterface(const nsIID& aIID, void** aResult) {
3442 nsresult rv;
3444 // Make sure to return ourselves for the channel event sink interface and
3445 // progress event sink interface, no matter what. We can forward these to
3446 // mNotificationCallbacks if it wants to get notifications for them. But we
3447 // need to see these notifications for proper functioning.
3448 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
3449 mChannelEventSink = do_GetInterface(mNotificationCallbacks);
3450 *aResult = static_cast<nsIChannelEventSink*>(EnsureXPCOMifier().take());
3451 return NS_OK;
3452 } else if (aIID.Equals(NS_GET_IID(nsIProgressEventSink))) {
3453 mProgressEventSink = do_GetInterface(mNotificationCallbacks);
3454 *aResult = static_cast<nsIProgressEventSink*>(EnsureXPCOMifier().take());
3455 return NS_OK;
3458 // Now give mNotificationCallbacks (if non-null) a chance to return the
3459 // desired interface.
3460 if (mNotificationCallbacks) {
3461 rv = mNotificationCallbacks->GetInterface(aIID, aResult);
3462 if (NS_SUCCEEDED(rv)) {
3463 NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!");
3464 return rv;
3468 if (!mFlagBackgroundRequest && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
3469 aIID.Equals(NS_GET_IID(nsIAuthPrompt2)))) {
3470 nsCOMPtr<nsIPromptFactory> wwatch =
3471 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
3472 NS_ENSURE_SUCCESS(rv, rv);
3474 // Get the an auth prompter for our window so that the parenting
3475 // of the dialogs works as it should when using tabs.
3476 nsCOMPtr<nsPIDOMWindowOuter> window;
3477 if (GetOwner()) {
3478 window = GetOwner()->GetOuterWindow();
3480 return wwatch->GetPrompt(window, aIID, reinterpret_cast<void**>(aResult));
3483 // Now check for the various XHR non-DOM interfaces, except
3484 // nsIProgressEventSink and nsIChannelEventSink which we already
3485 // handled above.
3486 if (aIID.Equals(NS_GET_IID(nsIStreamListener))) {
3487 *aResult = static_cast<nsIStreamListener*>(EnsureXPCOMifier().take());
3488 return NS_OK;
3490 if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) {
3491 *aResult = static_cast<nsIRequestObserver*>(EnsureXPCOMifier().take());
3492 return NS_OK;
3494 if (aIID.Equals(NS_GET_IID(nsITimerCallback))) {
3495 *aResult = static_cast<nsITimerCallback*>(EnsureXPCOMifier().take());
3496 return NS_OK;
3499 return QueryInterface(aIID, aResult);
3502 void XMLHttpRequestMainThread::GetInterface(
3503 JSContext* aCx, JS::Handle<JS::Value> aIID,
3504 JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
3505 dom::GetInterface(aCx, this, aIID, aRetval, aRv);
3508 XMLHttpRequestUpload* XMLHttpRequestMainThread::GetUpload(ErrorResult& aRv) {
3509 if (!mUpload) {
3510 mUpload = new XMLHttpRequestUpload(this);
3512 return mUpload;
3515 bool XMLHttpRequestMainThread::MozAnon() const { return mIsAnon; }
3517 bool XMLHttpRequestMainThread::MozSystem() const { return IsSystemXHR(); }
3519 void XMLHttpRequestMainThread::HandleTimeoutCallback() {
3520 if (mState == XMLHttpRequest_Binding::DONE) {
3521 MOZ_ASSERT_UNREACHABLE(
3522 "XMLHttpRequestMainThread::HandleTimeoutCallback "
3523 "with completed request");
3524 // do nothing!
3525 return;
3528 mFlagTimedOut = true;
3529 CloseRequestWithError(ProgressEventType::timeout);
3532 NS_IMETHODIMP
3533 XMLHttpRequestMainThread::Notify(nsITimer* aTimer) {
3534 if (mProgressNotifier == aTimer) {
3535 HandleProgressTimerCallback();
3536 return NS_OK;
3539 if (mTimeoutTimer == aTimer) {
3540 HandleTimeoutCallback();
3541 return NS_OK;
3544 if (mSyncTimeoutTimer == aTimer) {
3545 HandleSyncTimeoutTimer();
3546 return NS_OK;
3549 // Just in case some JS user wants to QI to nsITimerCallback and play with
3550 // us...
3551 NS_WARNING("Unexpected timer!");
3552 return NS_ERROR_INVALID_POINTER;
3555 void XMLHttpRequestMainThread::HandleProgressTimerCallback() {
3556 // Don't fire the progress event if mLoadTotal is 0, see XHR spec step 6.1
3557 if (!mLoadTotal && mLoadTransferred) {
3558 return;
3561 mProgressTimerIsActive = false;
3563 if (!mProgressSinceLastProgressEvent || mErrorLoad != ErrorType::eOK) {
3564 return;
3567 if (InUploadPhase()) {
3568 if (mUpload && !mUploadComplete && mFlagHadUploadListenersOnSend) {
3569 DispatchProgressEvent(mUpload, ProgressEventType::progress,
3570 mUploadTransferred, mUploadTotal);
3572 } else {
3573 FireReadystatechangeEvent();
3574 DispatchProgressEvent(this, ProgressEventType::progress, mLoadTransferred,
3575 mLoadTotal);
3578 mProgressSinceLastProgressEvent = false;
3580 StartProgressEventTimer();
3583 void XMLHttpRequestMainThread::StopProgressEventTimer() {
3584 if (mProgressNotifier) {
3585 mProgressTimerIsActive = false;
3586 mProgressNotifier->Cancel();
3590 void XMLHttpRequestMainThread::StartProgressEventTimer() {
3591 if (!mProgressNotifier) {
3592 mProgressNotifier = NS_NewTimer(GetTimerEventTarget());
3594 if (mProgressNotifier) {
3595 mProgressTimerIsActive = true;
3596 mProgressNotifier->Cancel();
3597 mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
3598 nsITimer::TYPE_ONE_SHOT);
3602 XMLHttpRequestMainThread::SyncTimeoutType
3603 XMLHttpRequestMainThread::MaybeStartSyncTimeoutTimer() {
3604 MOZ_ASSERT(mFlagSynchronous);
3606 Document* doc = GetDocumentIfCurrent();
3607 if (!doc || !doc->GetPageUnloadingEventTimeStamp()) {
3608 return eNoTimerNeeded;
3611 // If we are in a beforeunload or a unload event, we must force a timeout.
3612 TimeDuration diff =
3613 (TimeStamp::NowLoRes() - doc->GetPageUnloadingEventTimeStamp());
3614 if (diff.ToMilliseconds() > MAX_SYNC_TIMEOUT_WHEN_UNLOADING) {
3615 return eErrorOrExpired;
3618 mSyncTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
3619 if (!mSyncTimeoutTimer) {
3620 return eErrorOrExpired;
3623 uint32_t timeout = MAX_SYNC_TIMEOUT_WHEN_UNLOADING - diff.ToMilliseconds();
3624 nsresult rv = mSyncTimeoutTimer->InitWithCallback(this, timeout,
3625 nsITimer::TYPE_ONE_SHOT);
3626 return NS_FAILED(rv) ? eErrorOrExpired : eTimerStarted;
3629 void XMLHttpRequestMainThread::HandleSyncTimeoutTimer() {
3630 MOZ_ASSERT(mSyncTimeoutTimer);
3631 MOZ_ASSERT(mFlagSyncLooping);
3633 CancelSyncTimeoutTimer();
3634 Abort();
3637 void XMLHttpRequestMainThread::CancelSyncTimeoutTimer() {
3638 if (mSyncTimeoutTimer) {
3639 mSyncTimeoutTimer->Cancel();
3640 mSyncTimeoutTimer = nullptr;
3644 already_AddRefed<nsXMLHttpRequestXPCOMifier>
3645 XMLHttpRequestMainThread::EnsureXPCOMifier() {
3646 if (!mXPCOMifier) {
3647 mXPCOMifier = new nsXMLHttpRequestXPCOMifier(this);
3649 RefPtr<nsXMLHttpRequestXPCOMifier> newRef(mXPCOMifier);
3650 return newRef.forget();
3653 bool XMLHttpRequestMainThread::ShouldBlockAuthPrompt() {
3654 // Verify that it's ok to prompt for credentials here, per spec
3655 // http://xhr.spec.whatwg.org/#the-send%28%29-method
3657 if (mAuthorRequestHeaders.Has("authorization")) {
3658 return true;
3661 nsCOMPtr<nsIURI> uri;
3662 nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
3663 if (NS_WARN_IF(NS_FAILED(rv))) {
3664 return false;
3667 // Also skip if a username and/or password is provided in the URI.
3668 nsCString username;
3669 rv = uri->GetUsername(username);
3670 if (NS_WARN_IF(NS_FAILED(rv))) {
3671 return false;
3674 nsCString password;
3675 rv = uri->GetPassword(password);
3676 if (NS_WARN_IF(NS_FAILED(rv))) {
3677 return false;
3680 if (!username.IsEmpty() || !password.IsEmpty()) {
3681 return true;
3684 return false;
3687 void XMLHttpRequestMainThread::TruncateResponseText() {
3688 mResponseText.Truncate();
3689 XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
3692 NS_IMPL_ISUPPORTS(XMLHttpRequestMainThread::nsHeaderVisitor,
3693 nsIHttpHeaderVisitor)
3695 NS_IMETHODIMP XMLHttpRequestMainThread::nsHeaderVisitor::VisitHeader(
3696 const nsACString& header, const nsACString& value) {
3697 if (mXHR.IsSafeHeader(header, mHttpChannel)) {
3698 nsAutoCString lowerHeader(header);
3699 ToLowerCase(lowerHeader);
3700 if (!mHeaderList.InsertElementSorted(HeaderEntry(lowerHeader, value),
3701 fallible)) {
3702 return NS_ERROR_OUT_OF_MEMORY;
3705 return NS_OK;
3708 XMLHttpRequestMainThread::nsHeaderVisitor::nsHeaderVisitor(
3709 const XMLHttpRequestMainThread& aXMLHttpRequest,
3710 NotNull<nsIHttpChannel*> aHttpChannel)
3711 : mXHR(aXMLHttpRequest), mHttpChannel(aHttpChannel) {}
3713 XMLHttpRequestMainThread::nsHeaderVisitor::~nsHeaderVisitor() = default;
3715 void XMLHttpRequestMainThread::MaybeCreateBlobStorage() {
3716 MOZ_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
3718 if (mBlobStorage) {
3719 return;
3722 MutableBlobStorage::MutableBlobStorageType storageType =
3723 BasePrincipal::Cast(mPrincipal)->PrivateBrowsingId() == 0
3724 ? MutableBlobStorage::eCouldBeInTemporaryFile
3725 : MutableBlobStorage::eOnlyInMemory;
3727 nsCOMPtr<nsIEventTarget> eventTarget;
3728 if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
3729 eventTarget = global->EventTargetFor(TaskCategory::Other);
3732 mBlobStorage = new MutableBlobStorage(storageType, eventTarget);
3735 void XMLHttpRequestMainThread::BlobStoreCompleted(
3736 MutableBlobStorage* aBlobStorage, BlobImpl* aBlobImpl, nsresult aRv) {
3737 // Ok, the state is changed...
3738 if (mBlobStorage != aBlobStorage || NS_FAILED(aRv)) {
3739 return;
3742 MOZ_ASSERT(mState != XMLHttpRequest_Binding::DONE);
3744 mResponseBlobImpl = aBlobImpl;
3745 mBlobStorage = nullptr;
3747 ChangeStateToDone(mFlagSyncLooping);
3750 NS_IMETHODIMP
3751 XMLHttpRequestMainThread::GetName(nsACString& aName) {
3752 aName.AssignLiteral("XMLHttpRequest");
3753 return NS_OK;
3756 // nsXMLHttpRequestXPCOMifier implementation
3757 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpRequestXPCOMifier)
3758 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
3759 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
3760 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
3761 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
3762 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
3763 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
3764 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
3765 NS_INTERFACE_MAP_ENTRY(nsINamed)
3766 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
3767 NS_INTERFACE_MAP_END
3769 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpRequestXPCOMifier)
3770 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpRequestXPCOMifier)
3772 // Can't NS_IMPL_CYCLE_COLLECTION( because mXHR has ambiguous
3773 // inheritance from nsISupports.
3774 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequestXPCOMifier)
3776 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpRequestXPCOMifier)
3777 if (tmp->mXHR) {
3778 tmp->mXHR->mXPCOMifier = nullptr;
3780 NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR)
3781 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
3783 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpRequestXPCOMifier)
3784 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXHR)
3785 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
3787 NS_IMETHODIMP
3788 nsXMLHttpRequestXPCOMifier::GetInterface(const nsIID& aIID, void** aResult) {
3789 // Return ourselves for the things we implement (except
3790 // nsIInterfaceRequestor) and the XHR for the rest.
3791 if (!aIID.Equals(NS_GET_IID(nsIInterfaceRequestor))) {
3792 nsresult rv = QueryInterface(aIID, aResult);
3793 if (NS_SUCCEEDED(rv)) {
3794 return rv;
3798 return mXHR->GetInterface(aIID, aResult);
3801 ArrayBufferBuilder::ArrayBufferBuilder()
3802 : mMutex("ArrayBufferBuilder"),
3803 mDataPtr(nullptr),
3804 mCapacity(0),
3805 mLength(0),
3806 mMapPtr(nullptr),
3807 mNeutered(false) {}
3809 ArrayBufferBuilder::~ArrayBufferBuilder() {
3810 if (mDataPtr) {
3811 JS_free(nullptr, mDataPtr);
3814 if (mMapPtr) {
3815 JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
3816 mMapPtr = nullptr;
3819 mDataPtr = nullptr;
3820 mCapacity = mLength = 0;
3823 bool ArrayBufferBuilder::SetCapacity(uint32_t aNewCap) {
3824 MutexAutoLock lock(mMutex);
3825 return SetCapacityInternal(aNewCap, lock);
3828 bool ArrayBufferBuilder::SetCapacityInternal(
3829 uint32_t aNewCap, const MutexAutoLock& aProofOfLock) {
3830 MOZ_ASSERT(!mMapPtr);
3831 MOZ_ASSERT(!mNeutered);
3833 // To ensure that realloc won't free mDataPtr, use a size of 1
3834 // instead of 0.
3835 uint8_t* newdata = (uint8_t*)js_realloc(mDataPtr, aNewCap ? aNewCap : 1);
3837 if (!newdata) {
3838 return false;
3841 if (aNewCap > mCapacity) {
3842 memset(newdata + mCapacity, 0, aNewCap - mCapacity);
3845 mDataPtr = newdata;
3846 mCapacity = aNewCap;
3847 if (mLength > aNewCap) {
3848 mLength = aNewCap;
3851 return true;
3854 bool ArrayBufferBuilder::Append(const uint8_t* aNewData, uint32_t aDataLen,
3855 uint32_t aMaxGrowth) {
3856 MutexAutoLock lock(mMutex);
3857 MOZ_ASSERT(!mMapPtr);
3858 MOZ_ASSERT(!mNeutered);
3860 CheckedUint32 neededCapacity = mLength;
3861 neededCapacity += aDataLen;
3862 if (!neededCapacity.isValid()) {
3863 return false;
3865 if (mLength + aDataLen > mCapacity) {
3866 CheckedUint32 newcap = mCapacity;
3867 // Double while under aMaxGrowth or if not specified.
3868 if (!aMaxGrowth || mCapacity < aMaxGrowth) {
3869 newcap *= 2;
3870 } else {
3871 newcap += aMaxGrowth;
3874 if (!newcap.isValid()) {
3875 return false;
3878 // But make sure there's always enough to satisfy our request.
3879 if (newcap.value() < neededCapacity.value()) {
3880 newcap = neededCapacity;
3883 if (!SetCapacityInternal(newcap.value(), lock)) {
3884 return false;
3888 // Assert that the region isn't overlapping so we can memcpy.
3889 MOZ_ASSERT(
3890 !AreOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength, aDataLen));
3892 memcpy(mDataPtr + mLength, aNewData, aDataLen);
3893 mLength += aDataLen;
3895 return true;
3898 uint32_t ArrayBufferBuilder::Length() {
3899 MutexAutoLock lock(mMutex);
3900 MOZ_ASSERT(!mNeutered);
3901 return mLength;
3904 uint32_t ArrayBufferBuilder::Capacity() {
3905 MutexAutoLock lock(mMutex);
3906 MOZ_ASSERT(!mNeutered);
3907 return mCapacity;
3910 JSObject* ArrayBufferBuilder::TakeArrayBuffer(JSContext* aCx) {
3911 MutexAutoLock lock(mMutex);
3912 MOZ_DIAGNOSTIC_ASSERT(!mNeutered);
3914 if (mMapPtr) {
3915 JSObject* obj = JS::NewMappedArrayBufferWithContents(aCx, mLength, mMapPtr);
3916 if (!obj) {
3917 JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
3920 mMapPtr = nullptr;
3921 mNeutered = true;
3923 // The memory-mapped contents will be released when the ArrayBuffer becomes
3924 // detached or is GC'd.
3925 return obj;
3928 // we need to check for mLength == 0, because nothing may have been
3929 // added
3930 if (mCapacity > mLength || mLength == 0) {
3931 if (!SetCapacityInternal(mLength, lock)) {
3932 return nullptr;
3936 JSObject* obj = JS::NewArrayBufferWithContents(aCx, mLength, mDataPtr);
3937 if (!obj) {
3938 return nullptr;
3941 mDataPtr = nullptr;
3942 mCapacity = mLength = 0;
3944 mNeutered = true;
3945 return obj;
3948 nsresult ArrayBufferBuilder::MapToFileInPackage(const nsCString& aFile,
3949 nsIFile* aJarFile) {
3950 MutexAutoLock lock(mMutex);
3951 MOZ_ASSERT(NS_IsMainThread());
3952 MOZ_ASSERT(!mNeutered);
3954 nsresult rv;
3956 // Open Jar file to get related attributes of target file.
3957 RefPtr<nsZipArchive> zip = new nsZipArchive();
3958 rv = zip->OpenArchive(aJarFile);
3959 if (NS_FAILED(rv)) {
3960 return rv;
3962 nsZipItem* zipItem = zip->GetItem(aFile.get());
3963 if (!zipItem) {
3964 return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
3967 // If file was added to the package as stored(uncompressed), map to the
3968 // offset of file in zip package.
3969 if (!zipItem->Compression()) {
3970 uint32_t offset = zip->GetDataOffset(zipItem);
3971 uint32_t size = zipItem->RealSize();
3972 mozilla::AutoFDClose pr_fd;
3973 rv = aJarFile->OpenNSPRFileDesc(PR_RDONLY, 0, &pr_fd.rwget());
3974 if (NS_FAILED(rv)) {
3975 return rv;
3977 mMapPtr = JS::CreateMappedArrayBufferContents(
3978 PR_FileDesc2NativeHandle(pr_fd), offset, size);
3979 if (mMapPtr) {
3980 mLength = size;
3981 return NS_OK;
3984 return NS_ERROR_FAILURE;
3987 /* static */
3988 bool ArrayBufferBuilder::AreOverlappingRegions(const uint8_t* aStart1,
3989 uint32_t aLength1,
3990 const uint8_t* aStart2,
3991 uint32_t aLength2) {
3992 const uint8_t* end1 = aStart1 + aLength1;
3993 const uint8_t* end2 = aStart2 + aLength2;
3995 const uint8_t* max_start = aStart1 > aStart2 ? aStart1 : aStart2;
3996 const uint8_t* min_end = end1 < end2 ? end1 : end2;
3998 return max_start < min_end;
4001 RequestHeaders::RequestHeader* RequestHeaders::Find(const nsACString& aName) {
4002 for (RequestHeaders::RequestHeader& header : mHeaders) {
4003 if (header.mName.Equals(aName, nsCaseInsensitiveCStringComparator)) {
4004 return &header;
4007 return nullptr;
4010 bool RequestHeaders::IsEmpty() const { return mHeaders.IsEmpty(); }
4012 bool RequestHeaders::Has(const char* aName) {
4013 return Has(nsDependentCString(aName));
4016 bool RequestHeaders::Has(const nsACString& aName) { return !!Find(aName); }
4018 void RequestHeaders::Get(const char* aName, nsACString& aValue) {
4019 Get(nsDependentCString(aName), aValue);
4022 void RequestHeaders::Get(const nsACString& aName, nsACString& aValue) {
4023 RequestHeader* header = Find(aName);
4024 if (header) {
4025 aValue = header->mValue;
4026 } else {
4027 aValue.SetIsVoid(true);
4031 void RequestHeaders::Set(const char* aName, const nsACString& aValue) {
4032 Set(nsDependentCString(aName), aValue);
4035 void RequestHeaders::Set(const nsACString& aName, const nsACString& aValue) {
4036 RequestHeader* header = Find(aName);
4037 if (header) {
4038 header->mValue.Assign(aValue);
4039 } else {
4040 RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
4041 mHeaders.AppendElement(newHeader);
4045 void RequestHeaders::MergeOrSet(const char* aName, const nsACString& aValue) {
4046 MergeOrSet(nsDependentCString(aName), aValue);
4049 void RequestHeaders::MergeOrSet(const nsACString& aName,
4050 const nsACString& aValue) {
4051 RequestHeader* header = Find(aName);
4052 if (header) {
4053 header->mValue.AppendLiteral(", ");
4054 header->mValue.Append(aValue);
4055 } else {
4056 RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
4057 mHeaders.AppendElement(newHeader);
4061 void RequestHeaders::Clear() { mHeaders.Clear(); }
4063 void RequestHeaders::ApplyToChannel(nsIHttpChannel* aChannel,
4064 bool aStripRequestBodyHeader) const {
4065 for (const RequestHeader& header : mHeaders) {
4066 if (aStripRequestBodyHeader &&
4067 (header.mName.LowerCaseEqualsASCII("content-type") ||
4068 header.mName.LowerCaseEqualsASCII("content-encoding") ||
4069 header.mName.LowerCaseEqualsASCII("content-language") ||
4070 header.mName.LowerCaseEqualsASCII("content-location"))) {
4071 continue;
4073 // Update referrerInfo to override referrer header in system privileged.
4074 if (header.mName.LowerCaseEqualsASCII("referer")) {
4075 DebugOnly<nsresult> rv = aChannel->SetNewReferrerInfo(
4076 header.mValue, nsIReferrerInfo::ReferrerPolicyIDL::UNSAFE_URL, true);
4077 MOZ_ASSERT(NS_SUCCEEDED(rv));
4079 if (header.mValue.IsEmpty()) {
4080 DebugOnly<nsresult> rv = aChannel->SetEmptyRequestHeader(header.mName);
4081 MOZ_ASSERT(NS_SUCCEEDED(rv));
4082 } else {
4083 DebugOnly<nsresult> rv =
4084 aChannel->SetRequestHeader(header.mName, header.mValue, false);
4085 MOZ_ASSERT(NS_SUCCEEDED(rv));
4090 void RequestHeaders::GetCORSUnsafeHeaders(nsTArray<nsCString>& aArray) const {
4091 for (const RequestHeader& header : mHeaders) {
4092 if (!nsContentUtils::IsCORSSafelistedRequestHeader(header.mName,
4093 header.mValue)) {
4094 aArray.AppendElement(header.mName);
4099 RequestHeaders::CharsetIterator::CharsetIterator(nsACString& aSource)
4100 : mValid(false),
4101 mCurPos(-1),
4102 mCurLen(-1),
4103 mCutoff(aSource.Length()),
4104 mSource(aSource) {}
4106 bool RequestHeaders::CharsetIterator::Equals(
4107 const nsACString& aOther, const nsCStringComparator& aCmp) const {
4108 if (mValid) {
4109 return Substring(mSource, mCurPos, mCurLen).Equals(aOther, aCmp);
4110 } else {
4111 return false;
4115 void RequestHeaders::CharsetIterator::Replace(const nsACString& aReplacement) {
4116 if (mValid) {
4117 mSource.Replace(mCurPos, mCurLen, aReplacement);
4118 mCurLen = aReplacement.Length();
4122 bool RequestHeaders::CharsetIterator::Next() {
4123 int32_t start, end;
4124 nsAutoCString charset;
4126 // Look for another charset declaration in the string, limiting the
4127 // search to only the characters before the parts we've already searched
4128 // (before mCutoff), so that we don't find the same charset twice.
4129 NS_ExtractCharsetFromContentType(Substring(mSource, 0, mCutoff), charset,
4130 &mValid, &start, &end);
4132 if (!mValid) {
4133 return false;
4136 // Everything after the = sign is the part of the charset we want.
4137 mCurPos = mSource.FindChar('=', start) + 1;
4138 mCurLen = end - mCurPos;
4140 // Special case: the extracted charset is quoted with single quotes.
4141 // For the purpose of preserving what was set we want to handle them
4142 // as delimiters (although they aren't really).
4143 if (charset.Length() >= 2 && charset.First() == '\'' &&
4144 charset.Last() == '\'') {
4145 ++mCurPos;
4146 mCurLen -= 2;
4149 mCutoff = start;
4151 return true;
4154 } // namespace dom
4155 } // namespace mozilla