no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / dom / xhr / XMLHttpRequestMainThread.cpp
blobe18800e24a25789f4125bcaa01bd7a71e11b4a73
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "XMLHttpRequestMainThread.h"
9 #include <algorithm>
10 #ifndef XP_WIN
11 # include <unistd.h>
12 #endif
13 #include "mozilla/ArrayUtils.h"
14 #include "mozilla/BasePrincipal.h"
15 #include "mozilla/CheckedInt.h"
16 #include "mozilla/Components.h"
17 #include "mozilla/dom/AutoSuppressEventHandlingAndSuspend.h"
18 #include "mozilla/dom/BlobBinding.h"
19 #include "mozilla/dom/BlobURLProtocolHandler.h"
20 #include "mozilla/dom/DocGroup.h"
21 #include "mozilla/dom/DOMString.h"
22 #include "mozilla/dom/File.h"
23 #include "mozilla/dom/FileBinding.h"
24 #include "mozilla/dom/FileCreatorHelper.h"
25 #include "mozilla/dom/FetchUtil.h"
26 #include "mozilla/dom/FormData.h"
27 #include "mozilla/dom/quota/QuotaCommon.h"
28 #include "mozilla/dom/MutableBlobStorage.h"
29 #include "mozilla/dom/XMLDocument.h"
30 #include "mozilla/dom/URLSearchParams.h"
31 #include "mozilla/dom/UserActivation.h"
32 #include "mozilla/dom/Promise.h"
33 #include "mozilla/dom/PromiseNativeHandler.h"
34 #include "mozilla/dom/ReferrerInfo.h"
35 #include "mozilla/dom/WorkerError.h"
36 #include "mozilla/Encoding.h"
37 #include "mozilla/EventDispatcher.h"
38 #include "mozilla/EventListenerManager.h"
39 #include "mozilla/HoldDropJSObjects.h"
40 #include "mozilla/LoadInfo.h"
41 #include "mozilla/LoadContext.h"
42 #include "mozilla/MemoryReporting.h"
43 #include "mozilla/net/ContentRange.h"
44 #include "mozilla/PreloaderBase.h"
45 #include "mozilla/ScopeExit.h"
46 #include "mozilla/SpinEventLoopUntil.h"
47 #include "mozilla/StaticPrefs_dom.h"
48 #include "mozilla/StaticPrefs_network.h"
49 #include "mozilla/StaticPrefs_privacy.h"
50 #include "mozilla/dom/ProgressEvent.h"
51 #include "nsDataChannel.h"
52 #include "nsIBaseChannel.h"
53 #include "nsIJARChannel.h"
54 #include "nsIJARURI.h"
55 #include "nsReadableUtils.h"
56 #include "nsSandboxFlags.h"
58 #include "nsIURI.h"
59 #include "nsIURIMutator.h"
60 #include "nsILoadGroup.h"
61 #include "nsNetUtil.h"
62 #include "nsStringStream.h"
63 #include "nsIAuthPrompt.h"
64 #include "nsIAuthPrompt2.h"
65 #include "nsIClassOfService.h"
66 #include "nsIHttpChannel.h"
67 #include "nsISupportsPriority.h"
68 #include "nsIInterfaceRequestorUtils.h"
69 #include "nsStreamUtils.h"
70 #include "nsThreadUtils.h"
71 #include "nsIUploadChannel.h"
72 #include "nsIUploadChannel2.h"
73 #include "nsXPCOM.h"
74 #include "nsIDOMEventListener.h"
75 #include "nsVariant.h"
76 #include "nsIScriptError.h"
77 #include "nsICachingChannel.h"
78 #include "nsICookieJarSettings.h"
79 #include "nsContentUtils.h"
80 #include "nsCycleCollectionParticipant.h"
81 #include "nsError.h"
82 #include "nsIPromptFactory.h"
83 #include "nsIWindowWatcher.h"
84 #include "nsIConsoleService.h"
85 #include "nsAsyncRedirectVerifyHelper.h"
86 #include "nsStringBuffer.h"
87 #include "nsIFileChannel.h"
88 #include "mozilla/Telemetry.h"
89 #include "js/ArrayBuffer.h" // JS::{Create,Release}MappedArrayBufferContents,New{,Mapped}ArrayBufferWithContents
90 #include "js/JSON.h" // JS_ParseJSON
91 #include "js/MemoryFunctions.h"
92 #include "js/RootingAPI.h" // JS::{{,Mutable}Handle,Rooted}
93 #include "js/Value.h" // JS::{,Undefined}Value
94 #include "jsapi.h" // JS_ClearPendingException
95 #include "GeckoProfiler.h"
96 #include "mozilla/dom/XMLHttpRequestBinding.h"
97 #include "mozilla/Attributes.h"
98 #include "MultipartBlobImpl.h"
99 #include "nsIPermissionManager.h"
100 #include "nsMimeTypes.h"
101 #include "nsIHttpChannelInternal.h"
102 #include "nsCharSeparatedTokenizer.h"
103 #include "nsStreamListenerWrapper.h"
104 #include "nsITimedChannel.h"
105 #include "nsWrapperCacheInlines.h"
106 #include "nsZipArchive.h"
107 #include "mozilla/Preferences.h"
108 #include "private/pprio.h"
109 #include "XMLHttpRequestUpload.h"
111 // Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being
112 // replaced by FileCreatorHelper#CreateFileW.
113 #ifdef CreateFile
114 # undef CreateFile
115 #endif
117 extern mozilla::LazyLogModule gXMLHttpRequestLog;
119 using namespace mozilla::net;
121 namespace mozilla::dom {
123 using EventType = XMLHttpRequest::EventType;
124 using Events = XMLHttpRequest::Events;
126 // Maximum size that we'll grow an ArrayBuffer instead of doubling,
127 // once doubling reaches this threshold
128 const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH = 32 * 1024 * 1024;
129 // start at 32k to avoid lots of doubling right at the start
130 const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE = 32 * 1024;
131 // the maximum Content-Length that we'll preallocate. 1GB. Must fit
132 // in an int32_t!
133 const int32_t XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE =
134 1 * 1024 * 1024 * 1024LL;
136 namespace {
137 const nsString kLiteralString_readystatechange = u"readystatechange"_ns;
138 const nsString kLiteralString_xmlhttprequest = u"xmlhttprequest"_ns;
139 const nsString kLiteralString_DOMContentLoaded = u"DOMContentLoaded"_ns;
140 const nsCString kLiteralString_charset = "charset"_ns;
141 const nsCString kLiteralString_UTF_8 = "UTF-8"_ns;
142 } // namespace
144 #define NS_PROGRESS_EVENT_INTERVAL 50
145 #define MAX_SYNC_TIMEOUT_WHEN_UNLOADING 10000 /* 10 secs */
147 NS_IMPL_ISUPPORTS(nsXHRParseEndListener, nsIDOMEventListener)
149 class nsResumeTimeoutsEvent : public Runnable {
150 public:
151 explicit nsResumeTimeoutsEvent(nsPIDOMWindowInner* aWindow)
152 : Runnable("dom::nsResumeTimeoutsEvent"), mWindow(aWindow) {}
154 NS_IMETHOD Run() override {
155 mWindow->Resume();
156 return NS_OK;
159 private:
160 nsCOMPtr<nsPIDOMWindowInner> mWindow;
163 // This helper function adds the given load flags to the request's existing
164 // load flags.
165 static void AddLoadFlags(nsIRequest* request, nsLoadFlags newFlags) {
166 nsLoadFlags flags;
167 request->GetLoadFlags(&flags);
168 flags |= newFlags;
169 request->SetLoadFlags(flags);
172 // We are in a sync event loop.
173 #define NOT_CALLABLE_IN_SYNC_SEND_RV \
174 if (mFlagSyncLooping || mEventDispatchingSuspended) { \
175 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT); \
176 return; \
179 /////////////////////////////////////////////
182 /////////////////////////////////////////////
184 #ifdef DEBUG
186 // In debug mode, annotate WorkerRefs with the name of the function being
187 // invoked for increased scrutability. Save the previous value on the stack.
188 namespace {
189 struct DebugWorkerRefs {
190 Mutex& mMutex;
191 RefPtr<ThreadSafeWorkerRef> mTSWorkerRef;
192 nsCString mPrev;
194 DebugWorkerRefs(XMLHttpRequestMainThread& aXHR, const std::string& aStatus)
195 : mMutex(aXHR.mTSWorkerRefMutex) {
196 MutexAutoLock lock(mMutex);
198 mTSWorkerRef = aXHR.mTSWorkerRef;
200 if (!mTSWorkerRef) {
201 return;
204 MOZ_ASSERT(mTSWorkerRef->Private());
206 nsCString status(aStatus.c_str());
207 mPrev = GET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref());
208 SET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref(), status);
211 ~DebugWorkerRefs() {
212 MutexAutoLock lock(mMutex);
214 if (!mTSWorkerRef) {
215 return;
218 MOZ_ASSERT(mTSWorkerRef->Private());
220 SET_WORKERREF_DEBUG_STATUS(mTSWorkerRef->Ref(), mPrev);
222 mTSWorkerRef = nullptr;
225 } // namespace
227 # define STREAM_STRING(stuff) \
228 (((const std::ostringstream&)(std::ostringstream() << stuff)) \
229 .str()) // NOLINT
231 # if 1 // Disabling because bug 1855699
232 # define DEBUG_WORKERREFS void()
233 # define DEBUG_WORKERREFS1(x) void()
234 # else
236 # define DEBUG_WORKERREFS \
237 DebugWorkerRefs MOZ_UNIQUE_VAR(debugWR__)(*this, __func__)
239 # define DEBUG_WORKERREFS1(x) \
240 DebugWorkerRefs MOZ_UNIQUE_VAR(debugWR__)( \
241 *this, STREAM_STRING(__func__ << ": " << x)) // NOLINT
243 # endif
245 #else
246 # define DEBUG_WORKERREFS void()
247 # define DEBUG_WORKERREFS1(x) void()
248 #endif // DEBUG
250 bool XMLHttpRequestMainThread::sDontWarnAboutSyncXHR = false;
252 XMLHttpRequestMainThread::XMLHttpRequestMainThread(
253 nsIGlobalObject* aGlobalObject)
254 : XMLHttpRequest(aGlobalObject),
255 #ifdef DEBUG
256 mTSWorkerRefMutex("Debug WorkerRefs"),
257 #endif
258 mResponseBodyDecodedPos(0),
259 mResponseType(XMLHttpRequestResponseType::_empty),
260 mState(XMLHttpRequest_Binding::UNSENT),
261 mFlagSynchronous(false),
262 mFlagAborted(false),
263 mFlagParseBody(false),
264 mFlagSyncLooping(false),
265 mFlagBackgroundRequest(false),
266 mFlagHadUploadListenersOnSend(false),
267 mFlagACwithCredentials(false),
268 mFlagTimedOut(false),
269 mFlagDeleted(false),
270 mFlagSend(false),
271 mUploadTransferred(0),
272 mUploadTotal(0),
273 mUploadComplete(true),
274 mProgressSinceLastProgressEvent(false),
275 mRequestSentTime(0),
276 mTimeoutMilliseconds(0),
277 mErrorLoad(ErrorType::eOK),
278 mErrorLoadDetail(NS_OK),
279 mErrorParsingXML(false),
280 mWaitingForOnStopRequest(false),
281 mProgressTimerIsActive(false),
282 mIsHtml(false),
283 mWarnAboutSyncHtml(false),
284 mLoadTotal(-1),
285 mLoadTransferred(0),
286 mIsSystem(false),
287 mIsAnon(false),
288 mResultJSON(JS::UndefinedValue()),
289 mArrayBufferBuilder(new ArrayBufferBuilder()),
290 mResultArrayBuffer(nullptr),
291 mIsMappedArrayBuffer(false),
292 mXPCOMifier(nullptr),
293 mEventDispatchingSuspended(false),
294 mEofDecoded(false),
295 mDelayedDoneNotifier(nullptr) {
296 DEBUG_WORKERREFS;
297 mozilla::HoldJSObjects(this);
300 XMLHttpRequestMainThread::~XMLHttpRequestMainThread() {
301 DEBUG_WORKERREFS;
302 MOZ_ASSERT(
303 !mDelayedDoneNotifier,
304 "How can we have mDelayedDoneNotifier, which owns us, in destructor?");
306 mFlagDeleted = true;
308 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
309 mState == XMLHttpRequest_Binding::LOADING) {
310 Abort();
313 if (mParseEndListener) {
314 mParseEndListener->SetIsStale();
315 mParseEndListener = nullptr;
318 MOZ_ASSERT(!mFlagSyncLooping, "we rather crash than hang");
319 mFlagSyncLooping = false;
321 mozilla::DropJSObjects(this);
324 void XMLHttpRequestMainThread::Construct(
325 nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings,
326 bool aForWorker, nsIURI* aBaseURI /* = nullptr */,
327 nsILoadGroup* aLoadGroup /* = nullptr */,
328 PerformanceStorage* aPerformanceStorage /* = nullptr */,
329 nsICSPEventListener* aCSPEventListener /* = nullptr */) {
330 DEBUG_WORKERREFS;
331 MOZ_ASSERT(aPrincipal);
332 mPrincipal = aPrincipal;
333 mBaseURI = aBaseURI;
334 mLoadGroup = aLoadGroup;
335 mCookieJarSettings = aCookieJarSettings;
336 mForWorker = aForWorker;
337 mPerformanceStorage = aPerformanceStorage;
338 mCSPEventListener = aCSPEventListener;
341 void XMLHttpRequestMainThread::InitParameters(bool aAnon, bool aSystem) {
342 DEBUG_WORKERREFS;
343 if (!aAnon && !aSystem) {
344 return;
347 // Check for permissions.
348 // Chrome is always allowed access, so do the permission check only
349 // for non-chrome pages.
350 if (!IsSystemXHR() && aSystem) {
351 nsIGlobalObject* global = GetOwnerGlobal();
352 if (NS_WARN_IF(!global)) {
353 SetParameters(aAnon, false);
354 return;
357 nsIPrincipal* principal = global->PrincipalOrNull();
358 if (NS_WARN_IF(!principal)) {
359 SetParameters(aAnon, false);
360 return;
363 nsCOMPtr<nsIPermissionManager> permMgr =
364 components::PermissionManager::Service();
365 if (NS_WARN_IF(!permMgr)) {
366 SetParameters(aAnon, false);
367 return;
370 uint32_t permission;
371 nsresult rv = permMgr->TestPermissionFromPrincipal(
372 principal, "systemXHR"_ns, &permission);
373 if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) {
374 SetParameters(aAnon, false);
375 return;
379 SetParameters(aAnon, aSystem);
382 void XMLHttpRequestMainThread::SetClientInfoAndController(
383 const ClientInfo& aClientInfo,
384 const Maybe<ServiceWorkerDescriptor>& aController) {
385 mClientInfo.emplace(aClientInfo);
386 mController = aController;
389 void XMLHttpRequestMainThread::ResetResponse() {
390 mResponseXML = nullptr;
391 mResponseBody.Truncate();
392 TruncateResponseText();
393 mResponseBlobImpl = nullptr;
394 mResponseBlob = nullptr;
395 mBlobStorage = nullptr;
396 mResultArrayBuffer = nullptr;
397 mArrayBufferBuilder = new ArrayBufferBuilder();
398 mResultJSON.setUndefined();
399 mLoadTransferred = 0;
400 mResponseBodyDecodedPos = 0;
401 mEofDecoded = false;
404 NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestMainThread)
406 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestMainThread,
407 XMLHttpRequestEventTarget)
408 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
409 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
410 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML)
412 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener)
414 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseBlob)
415 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationCallbacks)
417 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink)
418 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink)
420 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
421 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
423 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestMainThread,
424 XMLHttpRequestEventTarget)
425 tmp->mResultArrayBuffer = nullptr;
426 tmp->mArrayBufferBuilder = nullptr;
427 tmp->mResultJSON.setUndefined();
428 tmp->mResponseBlobImpl = nullptr;
430 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
431 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
432 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML)
434 NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener)
436 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseBlob)
437 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationCallbacks)
439 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink)
440 NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink)
442 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
443 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
445 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestMainThread,
446 XMLHttpRequestEventTarget)
447 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
448 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultJSON)
449 NS_IMPL_CYCLE_COLLECTION_TRACE_END
451 bool XMLHttpRequestMainThread::IsCertainlyAliveForCC() const {
452 return mWaitingForOnStopRequest;
455 // QueryInterface implementation for XMLHttpRequestMainThread
456 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XMLHttpRequestMainThread)
457 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
458 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
459 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
460 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
461 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
462 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
463 NS_INTERFACE_MAP_ENTRY(nsINamed)
464 NS_INTERFACE_MAP_ENTRY(nsISizeOfEventTarget)
465 NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
467 NS_IMPL_ADDREF_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
468 NS_IMPL_RELEASE_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
470 void XMLHttpRequestMainThread::DisconnectFromOwner() {
471 XMLHttpRequestEventTarget::DisconnectFromOwner();
472 Abort();
475 size_t XMLHttpRequestMainThread::SizeOfEventTargetIncludingThis(
476 MallocSizeOf aMallocSizeOf) const {
477 size_t n = aMallocSizeOf(this);
478 n += mResponseBody.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
480 // Why is this safe? Because no-one else will report this string. The
481 // other possible sharers of this string are as follows.
483 // - The JS engine could hold copies if the JS code holds references, e.g.
484 // |var text = XHR.responseText|. However, those references will be via JS
485 // external strings, for which the JS memory reporter does *not* report the
486 // chars.
488 // - Binary extensions, but they're *extremely* unlikely to do any memory
489 // reporting.
491 n += mResponseText.SizeOfThis(aMallocSizeOf);
493 return n;
495 // Measurement of the following members may be added later if DMD finds it is
496 // worthwhile:
497 // - lots
500 static void LogMessage(
501 const char* aWarning, nsPIDOMWindowInner* aWindow,
502 const nsTArray<nsString>& aParams = nsTArray<nsString>()) {
503 nsCOMPtr<Document> doc;
504 if (aWindow) {
505 doc = aWindow->GetExtantDoc();
507 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, doc,
508 nsContentUtils::eDOM_PROPERTIES, aWarning,
509 aParams);
512 Document* XMLHttpRequestMainThread::GetResponseXML(ErrorResult& aRv) {
513 if (mResponseType != XMLHttpRequestResponseType::_empty &&
514 mResponseType != XMLHttpRequestResponseType::Document) {
515 aRv.ThrowInvalidStateError(
516 "responseXML is only available if responseType is '' or 'document'.");
517 return nullptr;
519 if (mWarnAboutSyncHtml) {
520 mWarnAboutSyncHtml = false;
521 LogMessage("HTMLSyncXHRWarning", GetOwner());
523 if (mState != XMLHttpRequest_Binding::DONE) {
524 return nullptr;
526 return mResponseXML;
530 * This piece copied from XMLDocument, we try to get the charset
531 * from HTTP headers.
533 nsresult XMLHttpRequestMainThread::DetectCharset() {
534 DEBUG_WORKERREFS;
535 mDecoder = nullptr;
537 if (mResponseType != XMLHttpRequestResponseType::_empty &&
538 mResponseType != XMLHttpRequestResponseType::Text &&
539 mResponseType != XMLHttpRequestResponseType::Json) {
540 return NS_OK;
543 nsAutoCString charsetVal;
544 const Encoding* encoding;
545 bool ok = mChannel && NS_SUCCEEDED(mChannel->GetContentCharset(charsetVal)) &&
546 (encoding = Encoding::ForLabel(charsetVal));
547 if (!ok) {
548 // MS documentation states UTF-8 is default for responseText
549 encoding = UTF_8_ENCODING;
552 if (mResponseType == XMLHttpRequestResponseType::Json &&
553 encoding != UTF_8_ENCODING) {
554 // The XHR spec says only UTF-8 is supported for responseType == "json"
555 LogMessage("JSONCharsetWarning", GetOwner());
556 encoding = UTF_8_ENCODING;
559 // Only sniff the BOM for non-JSON responseTypes
560 if (mResponseType == XMLHttpRequestResponseType::Json) {
561 mDecoder = encoding->NewDecoderWithBOMRemoval();
562 } else {
563 mDecoder = encoding->NewDecoder();
566 return NS_OK;
569 nsresult XMLHttpRequestMainThread::AppendToResponseText(
570 Span<const uint8_t> aBuffer, bool aLast) {
571 // Call this with an empty buffer to send the decoder the signal
572 // that we have hit the end of the stream.
574 NS_ENSURE_STATE(mDecoder);
576 CheckedInt<size_t> destBufferLen =
577 mDecoder->MaxUTF16BufferLength(aBuffer.Length());
579 { // scope for holding the mutex that protects mResponseText
580 XMLHttpRequestStringWriterHelper helper(mResponseText);
582 uint32_t len = helper.Length();
584 destBufferLen += len;
585 if (!destBufferLen.isValid() || destBufferLen.value() > UINT32_MAX) {
586 return NS_ERROR_OUT_OF_MEMORY;
589 auto handleOrErr = helper.BulkWrite(destBufferLen.value());
590 if (handleOrErr.isErr()) {
591 return handleOrErr.unwrapErr();
594 auto handle = handleOrErr.unwrap();
596 uint32_t result;
597 size_t read;
598 size_t written;
599 std::tie(result, read, written, std::ignore) =
600 mDecoder->DecodeToUTF16(aBuffer, handle.AsSpan().From(len), aLast);
601 MOZ_ASSERT(result == kInputEmpty);
602 MOZ_ASSERT(read == aBuffer.Length());
603 len += written;
604 MOZ_ASSERT(len <= destBufferLen.value());
605 handle.Finish(len, false);
606 } // release mutex
608 if (aLast) {
609 // Drop the finished decoder to avoid calling into a decoder
610 // that has finished.
611 mDecoder = nullptr;
612 mEofDecoded = true;
614 return NS_OK;
617 void XMLHttpRequestMainThread::GetResponseText(DOMString& aResponseText,
618 ErrorResult& aRv) {
619 MOZ_DIAGNOSTIC_ASSERT(!mForWorker);
621 XMLHttpRequestStringSnapshot snapshot;
622 GetResponseText(snapshot, aRv);
623 if (aRv.Failed()) {
624 return;
627 if (!snapshot.GetAsString(aResponseText)) {
628 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
629 return;
633 void XMLHttpRequestMainThread::GetResponseText(
634 XMLHttpRequestStringSnapshot& aSnapshot, ErrorResult& aRv) {
635 aSnapshot.Reset();
637 if (mResponseType != XMLHttpRequestResponseType::_empty &&
638 mResponseType != XMLHttpRequestResponseType::Text) {
639 aRv.ThrowInvalidStateError(
640 "responseText is only available if responseType is '' or 'text'.");
641 return;
644 if (mState != XMLHttpRequest_Binding::LOADING &&
645 mState != XMLHttpRequest_Binding::DONE) {
646 return;
649 // Main Fetch step 18 requires to ignore body for head/connect methods.
650 if (mRequestMethod.EqualsLiteral("HEAD") ||
651 mRequestMethod.EqualsLiteral("CONNECT")) {
652 return;
655 // We only decode text lazily if we're also parsing to a doc.
656 // Also, if we've decoded all current data already, then no need to decode
657 // more.
658 if ((!mResponseXML && !mErrorParsingXML) ||
659 (mResponseBodyDecodedPos == mResponseBody.Length() &&
660 (mState != XMLHttpRequest_Binding::DONE || mEofDecoded))) {
661 mResponseText.CreateSnapshot(aSnapshot);
662 return;
665 MatchCharsetAndDecoderToResponseDocument();
667 MOZ_ASSERT(mResponseBodyDecodedPos < mResponseBody.Length() ||
668 mState == XMLHttpRequest_Binding::DONE,
669 "Unexpected mResponseBodyDecodedPos");
670 Span<const uint8_t> span = mResponseBody;
671 aRv = AppendToResponseText(span.From(mResponseBodyDecodedPos),
672 mState == XMLHttpRequest_Binding::DONE);
673 if (aRv.Failed()) {
674 return;
677 mResponseBodyDecodedPos = mResponseBody.Length();
679 if (mEofDecoded) {
680 // Free memory buffer which we no longer need
681 mResponseBody.Truncate();
682 mResponseBodyDecodedPos = 0;
685 mResponseText.CreateSnapshot(aSnapshot);
688 nsresult XMLHttpRequestMainThread::CreateResponseParsedJSON(JSContext* aCx) {
689 if (!aCx) {
690 return NS_ERROR_FAILURE;
693 nsAutoString string;
694 nsresult rv = GetResponseTextForJSON(string);
695 if (NS_WARN_IF(NS_FAILED(rv))) {
696 return rv;
699 // The Unicode converter has already zapped the BOM if there was one
700 JS::Rooted<JS::Value> value(aCx);
701 if (!JS_ParseJSON(aCx, string.BeginReading(), string.Length(), &value)) {
702 return NS_ERROR_FAILURE;
705 mResultJSON = value;
706 return NS_OK;
709 void XMLHttpRequestMainThread::SetResponseType(
710 XMLHttpRequestResponseType aResponseType, ErrorResult& aRv) {
711 NOT_CALLABLE_IN_SYNC_SEND_RV
713 if (mState == XMLHttpRequest_Binding::LOADING ||
714 mState == XMLHttpRequest_Binding::DONE) {
715 aRv.ThrowInvalidStateError(
716 "Cannot set 'responseType' property on XMLHttpRequest after 'send()' "
717 "(when its state is LOADING or DONE).");
718 return;
721 // sync request is not allowed setting responseType in window context
722 if (HasOrHasHadOwner() && mState != XMLHttpRequest_Binding::UNSENT &&
723 mFlagSynchronous) {
724 LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
725 aRv.ThrowInvalidAccessError(
726 "synchronous XMLHttpRequests do not support timeout and responseType");
727 return;
730 // Set the responseType attribute's value to the given value.
731 SetResponseTypeRaw(aResponseType);
734 void XMLHttpRequestMainThread::GetResponse(
735 JSContext* aCx, JS::MutableHandle<JS::Value> aResponse, ErrorResult& aRv) {
736 MOZ_DIAGNOSTIC_ASSERT(!mForWorker);
738 switch (mResponseType) {
739 case XMLHttpRequestResponseType::_empty:
740 case XMLHttpRequestResponseType::Text: {
741 DOMString str;
742 GetResponseText(str, aRv);
743 if (aRv.Failed()) {
744 return;
746 if (!xpc::StringToJsval(aCx, str, aResponse)) {
747 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
749 return;
752 case XMLHttpRequestResponseType::Arraybuffer: {
753 if (mState != XMLHttpRequest_Binding::DONE) {
754 aResponse.setNull();
755 return;
758 if (!mResultArrayBuffer) {
759 mResultArrayBuffer = mArrayBufferBuilder->TakeArrayBuffer(aCx);
760 if (!mResultArrayBuffer) {
761 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
762 return;
765 aResponse.setObject(*mResultArrayBuffer);
766 return;
768 case XMLHttpRequestResponseType::Blob: {
769 if (mState != XMLHttpRequest_Binding::DONE) {
770 aResponse.setNull();
771 return;
774 if (!mResponseBlobImpl) {
775 aResponse.setNull();
776 return;
779 if (!mResponseBlob) {
780 mResponseBlob = Blob::Create(GetOwnerGlobal(), mResponseBlobImpl);
783 if (!GetOrCreateDOMReflector(aCx, mResponseBlob, aResponse)) {
784 aResponse.setNull();
787 return;
789 case XMLHttpRequestResponseType::Document: {
790 if (!mResponseXML || mState != XMLHttpRequest_Binding::DONE) {
791 aResponse.setNull();
792 return;
795 aRv =
796 nsContentUtils::WrapNative(aCx, ToSupports(mResponseXML), aResponse);
797 return;
799 case XMLHttpRequestResponseType::Json: {
800 if (mState != XMLHttpRequest_Binding::DONE) {
801 aResponse.setNull();
802 return;
805 if (mResultJSON.isUndefined()) {
806 aRv = CreateResponseParsedJSON(aCx);
807 TruncateResponseText();
808 if (aRv.Failed()) {
809 // Per spec, errors aren't propagated. null is returned instead.
810 aRv = NS_OK;
811 // It would be nice to log the error to the console. That's hard to
812 // do without calling window.onerror as a side effect, though.
813 JS_ClearPendingException(aCx);
814 mResultJSON.setNull();
817 aResponse.set(mResultJSON);
818 return;
820 default:
821 NS_ERROR("Should not happen");
824 aResponse.setNull();
827 already_AddRefed<BlobImpl> XMLHttpRequestMainThread::GetResponseBlobImpl() {
828 MOZ_DIAGNOSTIC_ASSERT(mForWorker);
829 MOZ_DIAGNOSTIC_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
831 if (mState != XMLHttpRequest_Binding::DONE) {
832 return nullptr;
835 RefPtr<BlobImpl> blobImpl = mResponseBlobImpl;
836 return blobImpl.forget();
839 already_AddRefed<ArrayBufferBuilder>
840 XMLHttpRequestMainThread::GetResponseArrayBufferBuilder() {
841 MOZ_DIAGNOSTIC_ASSERT(mForWorker);
842 MOZ_DIAGNOSTIC_ASSERT(mResponseType ==
843 XMLHttpRequestResponseType::Arraybuffer);
845 if (mState != XMLHttpRequest_Binding::DONE) {
846 return nullptr;
849 RefPtr<ArrayBufferBuilder> builder = mArrayBufferBuilder;
850 return builder.forget();
853 nsresult XMLHttpRequestMainThread::GetResponseTextForJSON(nsAString& aString) {
854 if (mState != XMLHttpRequest_Binding::DONE) {
855 aString.SetIsVoid(true);
856 return NS_OK;
859 if (!mResponseText.GetAsString(aString)) {
860 return NS_ERROR_OUT_OF_MEMORY;
863 return NS_OK;
866 bool XMLHttpRequestMainThread::IsCrossSiteCORSRequest() const {
867 if (!mChannel) {
868 return false;
871 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
872 return loadInfo->GetTainting() == LoadTainting::CORS;
875 bool XMLHttpRequestMainThread::IsDeniedCrossSiteCORSRequest() {
876 if (IsCrossSiteCORSRequest()) {
877 nsresult rv;
878 mChannel->GetStatus(&rv);
879 if (NS_FAILED(rv)) {
880 return true;
883 return false;
886 bool XMLHttpRequestMainThread::BadContentRangeRequested() {
887 if (!mChannel) {
888 return false;
890 // Only nsIBaseChannel supports this
891 nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel);
892 if (!baseChan) {
893 return false;
895 // A bad range was requested if the channel has no content range
896 // despite the request specifying a range header.
897 return !baseChan->ContentRange() && mAuthorRequestHeaders.Has("range");
900 RefPtr<mozilla::net::ContentRange>
901 XMLHttpRequestMainThread::GetRequestedContentRange() const {
902 MOZ_ASSERT(mChannel);
903 nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel);
904 if (!baseChan) {
905 return nullptr;
907 return baseChan->ContentRange();
910 void XMLHttpRequestMainThread::GetContentRangeHeader(nsACString& out) const {
911 if (!IsBlobURI(mRequestURL)) {
912 out.SetIsVoid(true);
913 return;
915 RefPtr<mozilla::net::ContentRange> range = GetRequestedContentRange();
916 if (range) {
917 range->AsHeader(out);
918 } else {
919 out.SetIsVoid(true);
923 void XMLHttpRequestMainThread::GetResponseURL(nsAString& aUrl) {
924 aUrl.Truncate();
926 if ((mState == XMLHttpRequest_Binding::UNSENT ||
927 mState == XMLHttpRequest_Binding::OPENED) ||
928 !mChannel) {
929 return;
932 // Make sure we don't leak responseURL information from denied cross-site
933 // requests.
934 if (IsDeniedCrossSiteCORSRequest()) {
935 return;
938 nsCOMPtr<nsIURI> responseUrl;
939 if (NS_FAILED(NS_GetFinalChannelURI(mChannel, getter_AddRefs(responseUrl)))) {
940 return;
943 nsAutoCString temp;
944 responseUrl->GetSpecIgnoringRef(temp);
945 CopyUTF8toUTF16(temp, aUrl);
948 uint32_t XMLHttpRequestMainThread::GetStatus(ErrorResult& aRv) {
949 // Make sure we don't leak status information from denied cross-site
950 // requests.
951 if (IsDeniedCrossSiteCORSRequest()) {
952 return 0;
955 if (mState == XMLHttpRequest_Binding::UNSENT ||
956 mState == XMLHttpRequest_Binding::OPENED) {
957 return 0;
960 if (mErrorLoad != ErrorType::eOK) {
961 // Let's simulate the http protocol for jar/app requests:
962 nsCOMPtr<nsIJARChannel> jarChannel = GetCurrentJARChannel();
963 if (jarChannel) {
964 nsresult status;
965 mChannel->GetStatus(&status);
967 if (status == NS_ERROR_FILE_NOT_FOUND) {
968 return 404; // Not Found
969 } else {
970 return 500; // Internal Error
974 return 0;
977 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
978 if (!httpChannel) {
979 // Pretend like we got a 200/206 response, since our load was successful
980 return GetRequestedContentRange() ? 206 : 200;
983 uint32_t status;
984 nsresult rv = httpChannel->GetResponseStatus(&status);
985 if (NS_FAILED(rv)) {
986 status = 0;
989 return status;
992 void XMLHttpRequestMainThread::GetStatusText(nsACString& aStatusText,
993 ErrorResult& aRv) {
994 // Return an empty status text on all error loads.
995 aStatusText.Truncate();
997 // Make sure we don't leak status information from denied cross-site
998 // requests.
999 if (IsDeniedCrossSiteCORSRequest()) {
1000 return;
1003 // Check the current XHR state to see if it is valid to obtain the statusText
1004 // value. This check is to prevent the status text for redirects from being
1005 // available before all the redirects have been followed and HTTP headers have
1006 // been received.
1007 if (mState == XMLHttpRequest_Binding::UNSENT ||
1008 mState == XMLHttpRequest_Binding::OPENED) {
1009 return;
1012 if (mErrorLoad != ErrorType::eOK) {
1013 return;
1016 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
1017 if (httpChannel) {
1018 Unused << httpChannel->GetResponseStatusText(aStatusText);
1019 } else {
1020 aStatusText.AssignLiteral("OK");
1024 void XMLHttpRequestMainThread::TerminateOngoingFetch(nsresult detail) {
1025 DEBUG_WORKERREFS;
1026 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
1027 mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
1028 mState == XMLHttpRequest_Binding::LOADING) {
1029 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Info,
1030 ("%p TerminateOngoingFetch(0x%" PRIx32 ")", this,
1031 static_cast<uint32_t>(detail)));
1032 CloseRequest(detail);
1036 void XMLHttpRequestMainThread::CloseRequest(nsresult detail) {
1037 DEBUG_WORKERREFS;
1038 mWaitingForOnStopRequest = false;
1039 mErrorLoad = ErrorType::eTerminated;
1040 mErrorLoadDetail = detail;
1041 if (mChannel) {
1042 mChannel->CancelWithReason(NS_BINDING_ABORTED,
1043 "XMLHttpRequestMainThread::CloseRequest"_ns);
1045 CancelTimeoutTimer();
1048 void XMLHttpRequestMainThread::CloseRequestWithError(
1049 const ErrorProgressEventType& aType) {
1050 DEBUG_WORKERREFS;
1051 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
1052 ("%p CloseRequestWithError(%s)", this, aType.cStr));
1054 CloseRequest(aType.errorCode);
1056 ResetResponse();
1058 // If we're in the destructor, don't risk dispatching an event.
1059 if (mFlagDeleted) {
1060 mFlagSyncLooping = false;
1061 return;
1064 if (mState != XMLHttpRequest_Binding::UNSENT &&
1065 !(mState == XMLHttpRequest_Binding::OPENED && !mFlagSend) &&
1066 mState != XMLHttpRequest_Binding::DONE) {
1067 ChangeState(XMLHttpRequest_Binding::DONE, true);
1069 if (!mFlagSyncLooping) {
1070 if (mUpload && !mUploadComplete) {
1071 mUploadComplete = true;
1072 DispatchProgressEvent(mUpload, aType, 0, -1);
1074 DispatchProgressEvent(this, aType, 0, -1);
1078 // The ChangeState call above calls onreadystatechange handlers which
1079 // if they load a new url will cause XMLHttpRequestMainThread::Open to clear
1080 // the abort state bit. If this occurs we're not uninitialized (bug 361773).
1081 if (mFlagAborted) {
1082 ChangeState(XMLHttpRequest_Binding::UNSENT, false); // IE seems to do it
1085 mFlagSyncLooping = false;
1088 void XMLHttpRequestMainThread::RequestErrorSteps(
1089 const ProgressEventType aEventType, const nsresult aOptionalException,
1090 ErrorResult& aRv) {
1091 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
1092 ("%p RequestErrorSteps(%s,0x%" PRIx32 ")", this, aEventType.cStr,
1093 static_cast<uint32_t>(aOptionalException)));
1095 // Cancel our timers first before setting our state to done, so we don't
1096 // trip any assertions if one fires and asserts that state != done.
1097 CancelTimeoutTimer();
1098 CancelSyncTimeoutTimer();
1099 StopProgressEventTimer();
1101 // Step 1
1102 mState = XMLHttpRequest_Binding::DONE;
1104 // Step 2
1105 mFlagSend = false;
1107 // Step 3
1108 ResetResponse();
1110 // If we're in the destructor, don't risk dispatching an event.
1111 if (mFlagDeleted) {
1112 mFlagSyncLooping = false;
1113 return;
1116 // Step 4
1117 if (mFlagSynchronous && NS_FAILED(aOptionalException)) {
1118 aRv.Throw(aOptionalException);
1119 return;
1122 // Step 5
1123 FireReadystatechangeEvent();
1125 // Step 6
1126 if (mUpload && !mUploadComplete) {
1127 // Step 6-1
1128 mUploadComplete = true;
1130 // Step 6-2
1131 if (mFlagHadUploadListenersOnSend) {
1132 // Steps 6-3, 6-4 (loadend is fired for us)
1133 DispatchProgressEvent(mUpload, aEventType, 0, -1);
1137 // Steps 7 and 8 (loadend is fired for us)
1138 DispatchProgressEvent(this, aEventType, 0, -1);
1141 void XMLHttpRequestMainThread::Abort(ErrorResult& aRv) {
1142 NOT_CALLABLE_IN_SYNC_SEND_RV
1143 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("%p Abort()", this));
1144 AbortInternal(aRv);
1147 void XMLHttpRequestMainThread::AbortInternal(ErrorResult& aRv) {
1148 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug, ("%p AbortInternal()", this));
1149 mFlagAborted = true;
1150 DisconnectDoneNotifier();
1152 // Step 1
1153 TerminateOngoingFetch(NS_ERROR_DOM_ABORT_ERR);
1155 // Step 2
1156 if ((mState == XMLHttpRequest_Binding::OPENED && mFlagSend) ||
1157 mState == XMLHttpRequest_Binding::HEADERS_RECEIVED ||
1158 mState == XMLHttpRequest_Binding::LOADING) {
1159 RequestErrorSteps(Events::abort, NS_ERROR_DOM_ABORT_ERR, aRv);
1162 // Step 3
1163 if (mState == XMLHttpRequest_Binding::DONE) {
1164 ChangeState(XMLHttpRequest_Binding::UNSENT,
1165 false); // no ReadystateChange event
1168 mFlagSyncLooping = false;
1171 /*Method that checks if it is safe to expose a header value to the client.
1172 It is used to check what headers are exposed for CORS requests.*/
1173 bool XMLHttpRequestMainThread::IsSafeHeader(
1174 const nsACString& aHeader, NotNull<nsIHttpChannel*> aHttpChannel) const {
1175 // See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
1176 if (!IsSystemXHR() && nsContentUtils::IsForbiddenResponseHeader(aHeader)) {
1177 NS_WARNING("blocked access to response header");
1178 return false;
1180 // if this is not a CORS call all headers are safe
1181 if (!IsCrossSiteCORSRequest()) {
1182 return true;
1184 // Check for dangerous headers
1185 // Make sure we don't leak header information from denied cross-site
1186 // requests.
1187 if (mChannel) {
1188 nsresult status;
1189 mChannel->GetStatus(&status);
1190 if (NS_FAILED(status)) {
1191 return false;
1194 const char* kCrossOriginSafeHeaders[] = {
1195 "cache-control", "content-language", "content-type", "content-length",
1196 "expires", "last-modified", "pragma"};
1197 for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
1198 if (aHeader.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
1199 return true;
1202 nsAutoCString headerVal;
1203 // The "Access-Control-Expose-Headers" header contains a comma separated
1204 // list of method names.
1205 Unused << aHttpChannel->GetResponseHeader("Access-Control-Expose-Headers"_ns,
1206 headerVal);
1207 bool isSafe = false;
1208 for (const nsACString& token :
1209 nsCCharSeparatedTokenizer(headerVal, ',').ToRange()) {
1210 if (token.IsEmpty()) {
1211 continue;
1213 if (!NS_IsValidHTTPToken(token)) {
1214 return false;
1217 if (token.EqualsLiteral("*") && !mFlagACwithCredentials) {
1218 isSafe = true;
1219 } else if (aHeader.Equals(token, nsCaseInsensitiveCStringComparator)) {
1220 isSafe = true;
1224 return isSafe;
1227 bool XMLHttpRequestMainThread::GetContentType(nsACString& aValue) const {
1228 MOZ_ASSERT(mChannel);
1229 nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel);
1230 if (baseChan) {
1231 RefPtr<CMimeType> fullMimeType(baseChan->FullMimeType());
1232 if (fullMimeType) {
1233 fullMimeType->Serialize(aValue);
1234 return true;
1237 if (NS_SUCCEEDED(mChannel->GetContentType(aValue))) {
1238 nsCString value;
1239 if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) {
1240 aValue.AppendLiteral(";charset=");
1241 aValue.Append(value);
1243 return true;
1245 return false;
1247 void XMLHttpRequestMainThread::GetAllResponseHeaders(
1248 nsACString& aResponseHeaders, ErrorResult& aRv) {
1249 NOT_CALLABLE_IN_SYNC_SEND_RV
1251 aResponseHeaders.Truncate();
1253 // If the state is UNSENT or OPENED,
1254 // return the empty string and terminate these steps.
1255 if (mState == XMLHttpRequest_Binding::UNSENT ||
1256 mState == XMLHttpRequest_Binding::OPENED) {
1257 return;
1260 if (mErrorLoad != ErrorType::eOK) {
1261 return;
1264 if (nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel()) {
1265 RefPtr<nsHeaderVisitor> visitor =
1266 new nsHeaderVisitor(*this, WrapNotNull(httpChannel));
1267 if (NS_SUCCEEDED(httpChannel->VisitResponseHeaders(visitor))) {
1268 aResponseHeaders = visitor->Headers();
1270 return;
1273 if (!mChannel) {
1274 return;
1277 // Even non-http channels supply content type.
1278 nsAutoCString value;
1279 if (GetContentType(value)) {
1280 aResponseHeaders.AppendLiteral("Content-Type: ");
1281 aResponseHeaders.Append(value);
1282 aResponseHeaders.AppendLiteral("\r\n");
1285 // Don't provide Content-Length for data URIs
1286 nsCOMPtr<nsIURI> uri;
1287 if (NS_FAILED(mChannel->GetURI(getter_AddRefs(uri))) ||
1288 !uri->SchemeIs("data")) {
1289 int64_t length;
1290 if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1291 aResponseHeaders.AppendLiteral("Content-Length: ");
1292 aResponseHeaders.AppendInt(length);
1293 aResponseHeaders.AppendLiteral("\r\n");
1297 // Should set a Content-Range header for blob scheme.
1298 // From https://fetch.spec.whatwg.org/#scheme-fetch 3.blob.9.20:
1299 // "Set response’s header list to «(`Content-Length`, serializedSlicedLength),
1300 // (`Content-Type`, type), (`Content-Range`, contentRange)»."
1301 GetContentRangeHeader(value);
1302 if (!value.IsVoid()) {
1303 aResponseHeaders.AppendLiteral("Content-Range: ");
1304 aResponseHeaders.Append(value);
1305 aResponseHeaders.AppendLiteral("\r\n");
1309 void XMLHttpRequestMainThread::GetResponseHeader(const nsACString& header,
1310 nsACString& _retval,
1311 ErrorResult& aRv) {
1312 NOT_CALLABLE_IN_SYNC_SEND_RV
1314 _retval.SetIsVoid(true);
1316 nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
1318 if (!httpChannel) {
1319 // If the state is UNSENT or OPENED,
1320 // return null and terminate these steps.
1321 if (mState == XMLHttpRequest_Binding::UNSENT ||
1322 mState == XMLHttpRequest_Binding::OPENED) {
1323 return;
1326 // Even non-http channels supply content type and content length.
1327 // Remember we don't leak header information from denied cross-site
1328 // requests. However, we handle file: and blob: URLs for blob response
1329 // types by canceling them with a specific error, so we have to allow
1330 // them to pass through this check.
1331 nsresult status;
1332 if (!mChannel || NS_FAILED(mChannel->GetStatus(&status)) ||
1333 (NS_FAILED(status) && status != NS_ERROR_FILE_ALREADY_EXISTS)) {
1334 return;
1337 // Content Type:
1338 if (header.LowerCaseEqualsASCII("content-type")) {
1339 if (!GetContentType(_retval)) {
1340 // Means no content type
1341 _retval.SetIsVoid(true);
1342 return;
1346 // Content Length:
1347 else if (header.LowerCaseEqualsASCII("content-length")) {
1348 int64_t length;
1349 if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
1350 _retval.AppendInt(length);
1354 // Content Range:
1355 else if (header.LowerCaseEqualsASCII("content-range")) {
1356 GetContentRangeHeader(_retval);
1359 return;
1362 // Check for dangerous headers
1363 if (!IsSafeHeader(header, WrapNotNull(httpChannel))) {
1364 return;
1367 aRv = httpChannel->GetResponseHeader(header, _retval);
1368 if (aRv.ErrorCodeIs(NS_ERROR_NOT_AVAILABLE)) {
1369 // Means no header
1370 _retval.SetIsVoid(true);
1371 aRv.SuppressException();
1375 already_AddRefed<nsILoadGroup> XMLHttpRequestMainThread::GetLoadGroup() const {
1376 if (mFlagBackgroundRequest) {
1377 return nullptr;
1380 if (mLoadGroup) {
1381 nsCOMPtr<nsILoadGroup> ref = mLoadGroup;
1382 return ref.forget();
1385 Document* doc = GetDocumentIfCurrent();
1386 if (doc) {
1387 return doc->GetDocumentLoadGroup();
1390 return nullptr;
1393 nsresult XMLHttpRequestMainThread::FireReadystatechangeEvent() {
1394 MOZ_ASSERT(mState != XMLHttpRequest_Binding::UNSENT);
1395 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1396 event->InitEvent(kLiteralString_readystatechange, false, false);
1397 // We assume anyone who managed to call CreateReadystatechangeEvent is trusted
1398 event->SetTrusted(true);
1399 DispatchOrStoreEvent(this, event);
1400 return NS_OK;
1403 void XMLHttpRequestMainThread::DispatchProgressEvent(
1404 DOMEventTargetHelper* aTarget, const ProgressEventType& aType,
1405 int64_t aLoaded, int64_t aTotal) {
1406 DEBUG_WORKERREFS;
1407 NS_ASSERTION(aTarget, "null target");
1409 if (NS_FAILED(CheckCurrentGlobalCorrectness()) ||
1410 (!AllowUploadProgress() && aTarget == mUpload)) {
1411 return;
1414 // If blocked by CORS, zero-out the stats on progress events
1415 // and never fire "progress" or "load" events at all.
1416 if (IsDeniedCrossSiteCORSRequest()) {
1417 if (aType == Events::progress || aType == Events::load) {
1418 return;
1420 aLoaded = 0;
1421 aTotal = -1;
1424 ProgressEventInit init;
1425 init.mBubbles = false;
1426 init.mCancelable = false;
1427 init.mLengthComputable = aTotal != -1; // XHR spec step 6.1
1428 init.mLoaded = aLoaded;
1429 init.mTotal = (aTotal == -1) ? 0 : aTotal;
1431 RefPtr<ProgressEvent> event =
1432 ProgressEvent::Constructor(aTarget, aType, init);
1433 event->SetTrusted(true);
1435 MOZ_LOG(
1436 gXMLHttpRequestLog, LogLevel::Debug,
1437 ("firing %s event (%u,%u,%" PRIu64 ",%" PRIu64 ")", aType.cStr,
1438 aTarget == mUpload, aTotal != -1, aLoaded, (aTotal == -1) ? 0 : aTotal));
1440 DispatchOrStoreEvent(aTarget, event);
1442 // If we're sending a load, error, timeout or abort event, then
1443 // also dispatch the subsequent loadend event.
1444 if (aType == Events::load || aType == Events::error ||
1445 aType == Events::timeout || aType == Events::abort) {
1446 DispatchProgressEvent(aTarget, Events::loadend, aLoaded, aTotal);
1450 void XMLHttpRequestMainThread::DispatchOrStoreEvent(
1451 DOMEventTargetHelper* aTarget, Event* aEvent) {
1452 DEBUG_WORKERREFS;
1453 MOZ_ASSERT(aTarget);
1454 MOZ_ASSERT(aEvent);
1456 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
1457 return;
1460 if (mEventDispatchingSuspended) {
1461 PendingEvent* event = mPendingEvents.AppendElement();
1462 event->mTarget = aTarget;
1463 event->mEvent = aEvent;
1464 return;
1467 aTarget->DispatchEvent(*aEvent);
1470 void XMLHttpRequestMainThread::SuspendEventDispatching() {
1471 MOZ_ASSERT(!mEventDispatchingSuspended);
1472 mEventDispatchingSuspended = true;
1475 void XMLHttpRequestMainThread::ResumeEventDispatching() {
1476 MOZ_ASSERT(mEventDispatchingSuspended);
1477 mEventDispatchingSuspended = false;
1479 nsTArray<PendingEvent> pendingEvents = std::move(mPendingEvents);
1481 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
1482 return;
1485 for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
1486 pendingEvents[i].mTarget->DispatchEvent(*pendingEvents[i].mEvent);
1490 already_AddRefed<nsIHttpChannel>
1491 XMLHttpRequestMainThread::GetCurrentHttpChannel() {
1492 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
1493 return httpChannel.forget();
1496 already_AddRefed<nsIJARChannel>
1497 XMLHttpRequestMainThread::GetCurrentJARChannel() {
1498 nsCOMPtr<nsIJARChannel> appChannel = do_QueryInterface(mChannel);
1499 return appChannel.forget();
1502 bool XMLHttpRequestMainThread::IsSystemXHR() const {
1503 return mIsSystem || mPrincipal->IsSystemPrincipal();
1506 bool XMLHttpRequestMainThread::InUploadPhase() const {
1507 // We're in the upload phase while our state is OPENED.
1508 return mState == XMLHttpRequest_Binding::OPENED;
1511 // This case is hit when the async parameter is outright omitted, which
1512 // should set it to true (and the username and password to null).
1513 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1514 const nsAString& aUrl, ErrorResult& aRv) {
1515 Open(aMethod, aUrl, true, VoidString(), VoidString(), aRv);
1518 // This case is hit when the async parameter is specified, even if the
1519 // JS value was "undefined" (which due to legacy reasons should be
1520 // treated as true, which is how it will already be passed in here).
1521 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1522 const nsAString& aUrl, bool aAsync,
1523 const nsAString& aUsername,
1524 const nsAString& aPassword,
1525 ErrorResult& aRv) {
1526 Open(aMethod, NS_ConvertUTF16toUTF8(aUrl), aAsync, aUsername, aPassword, aRv);
1529 void XMLHttpRequestMainThread::Open(const nsACString& aMethod,
1530 const nsACString& aUrl, bool aAsync,
1531 const nsAString& aUsername,
1532 const nsAString& aPassword,
1533 ErrorResult& aRv) {
1534 DEBUG_WORKERREFS1(aMethod << " " << aUrl);
1535 NOT_CALLABLE_IN_SYNC_SEND_RV
1537 // Gecko-specific
1538 if (!aAsync && !DontWarnAboutSyncXHR() && GetOwner() &&
1539 GetOwner()->GetExtantDoc()) {
1540 GetOwner()->GetExtantDoc()->WarnOnceAbout(
1541 DeprecatedOperations::eSyncXMLHttpRequestDeprecated);
1544 Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC,
1545 aAsync ? 0 : 1);
1547 // Step 1
1548 nsCOMPtr<Document> responsibleDocument = GetDocumentIfCurrent();
1549 if (!responsibleDocument) {
1550 // This could be because we're no longer current or because we're in some
1551 // non-window context...
1552 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
1553 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
1554 return;
1557 if (!mPrincipal) {
1558 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
1559 return;
1562 // Gecko-specific
1563 if (!aAsync && responsibleDocument && GetOwner()) {
1564 // We have no extant document during unload, so the above general
1565 // syncXHR warning will not display. But we do want to display a
1566 // recommendation to use sendBeacon instead of syncXHR during unload.
1567 nsCOMPtr<nsIDocShell> shell = responsibleDocument->GetDocShell();
1568 if (shell) {
1569 bool inUnload = false;
1570 shell->GetIsInUnload(&inUnload);
1571 if (inUnload) {
1572 LogMessage("UseSendBeaconDuringUnloadAndPagehideWarning", GetOwner());
1577 // Steps 2-4
1578 nsAutoCString method;
1579 aRv = FetchUtil::GetValidRequestMethod(aMethod, method);
1580 if (NS_WARN_IF(aRv.Failed())) {
1581 return;
1584 // Steps 5-6
1585 nsIURI* baseURI = nullptr;
1586 if (mBaseURI) {
1587 baseURI = mBaseURI;
1588 } else if (responsibleDocument) {
1589 baseURI = responsibleDocument->GetBaseURI();
1592 // Use the responsible document's encoding for the URL if we have one,
1593 // except for dedicated workers. Use UTF-8 otherwise.
1594 NotNull<const Encoding*> originCharset = UTF_8_ENCODING;
1595 if (responsibleDocument &&
1596 responsibleDocument->NodePrincipal() == mPrincipal) {
1597 originCharset = responsibleDocument->GetDocumentCharacterSet();
1600 nsCOMPtr<nsIURI> parsedURL;
1601 nsresult rv =
1602 NS_NewURI(getter_AddRefs(parsedURL), aUrl, originCharset, baseURI);
1603 if (NS_FAILED(rv)) {
1604 aRv.ThrowSyntaxError("'"_ns + aUrl + "' is not a valid URL."_ns);
1605 return;
1607 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
1608 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
1609 return;
1612 // Step 7
1613 // This is already handled by the other Open() method, which passes
1614 // username and password in as NullStrings.
1616 // Step 8
1617 nsAutoCString host;
1618 parsedURL->GetHost(host);
1619 if (!host.IsEmpty() && (!aUsername.IsVoid() || !aPassword.IsVoid())) {
1620 auto mutator = NS_MutateURI(parsedURL);
1621 if (!aUsername.IsVoid()) {
1622 mutator.SetUsername(NS_ConvertUTF16toUTF8(aUsername));
1624 if (!aPassword.IsVoid()) {
1625 mutator.SetPassword(NS_ConvertUTF16toUTF8(aPassword));
1627 Unused << mutator.Finalize(parsedURL);
1630 // Step 9
1631 if (!aAsync && HasOrHasHadOwner() &&
1632 (mTimeoutMilliseconds ||
1633 mResponseType != XMLHttpRequestResponseType::_empty)) {
1634 if (mTimeoutMilliseconds) {
1635 LogMessage("TimeoutSyncXHRWarning", GetOwner());
1637 if (mResponseType != XMLHttpRequestResponseType::_empty) {
1638 LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
1640 aRv.ThrowInvalidAccessError(
1641 "synchronous XMLHttpRequests do not support timeout and responseType");
1642 return;
1645 // Step 10
1646 TerminateOngoingFetch(NS_OK);
1648 // Step 11
1649 // timeouts are handled without a flag
1650 DisconnectDoneNotifier();
1651 mFlagSend = false;
1652 mRequestMethod.Assign(method);
1653 mRequestURL = parsedURL;
1654 mFlagSynchronous = !aAsync;
1655 mAuthorRequestHeaders.Clear();
1656 ResetResponse();
1658 // Gecko-specific
1659 mFlagHadUploadListenersOnSend = false;
1660 mFlagAborted = false;
1661 mFlagTimedOut = false;
1662 mDecoder = nullptr;
1664 // Per spec we should only create the channel on send(), but we have internal
1665 // code that relies on the channel being created now, and that code is not
1666 // always IsSystemXHR(). However, we're not supposed to throw channel-creation
1667 // errors during open(), so we silently ignore those here.
1668 CreateChannel();
1670 // Step 12
1671 if (mState != XMLHttpRequest_Binding::OPENED) {
1672 mState = XMLHttpRequest_Binding::OPENED;
1673 FireReadystatechangeEvent();
1677 void XMLHttpRequestMainThread::SetOriginAttributes(
1678 const OriginAttributesDictionary& aAttrs) {
1679 MOZ_ASSERT((mState == XMLHttpRequest_Binding::OPENED) && !mFlagSend);
1681 OriginAttributes attrs(aAttrs);
1683 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
1684 loadInfo->SetOriginAttributes(attrs);
1688 * "Copy" from a stream.
1690 nsresult XMLHttpRequestMainThread::StreamReaderFunc(
1691 nsIInputStream* in, void* closure, const char* fromRawSegment,
1692 uint32_t toOffset, uint32_t count, uint32_t* writeCount) {
1693 XMLHttpRequestMainThread* xmlHttpRequest =
1694 static_cast<XMLHttpRequestMainThread*>(closure);
1695 if (!xmlHttpRequest || !writeCount) {
1696 NS_WARNING(
1697 "XMLHttpRequest cannot read from stream: no closure or writeCount");
1698 return NS_ERROR_FAILURE;
1701 nsresult rv = NS_OK;
1703 if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Blob) {
1704 xmlHttpRequest->MaybeCreateBlobStorage();
1705 rv = xmlHttpRequest->mBlobStorage->Append(fromRawSegment, count);
1706 } else if (xmlHttpRequest->mResponseType ==
1707 XMLHttpRequestResponseType::Arraybuffer &&
1708 !xmlHttpRequest->mIsMappedArrayBuffer) {
1709 // get the initial capacity to something reasonable to avoid a bunch of
1710 // reallocs right at the start
1711 if (xmlHttpRequest->mArrayBufferBuilder->Capacity() == 0)
1712 xmlHttpRequest->mArrayBufferBuilder->SetCapacity(
1713 std::max(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE));
1715 if (NS_WARN_IF(!xmlHttpRequest->mArrayBufferBuilder->Append(
1716 reinterpret_cast<const uint8_t*>(fromRawSegment), count,
1717 XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH))) {
1718 return NS_ERROR_OUT_OF_MEMORY;
1721 } else if (xmlHttpRequest->mResponseType ==
1722 XMLHttpRequestResponseType::_empty &&
1723 xmlHttpRequest->mResponseXML) {
1724 // Copy for our own use
1725 if (!xmlHttpRequest->mResponseBody.Append(fromRawSegment, count,
1726 fallible)) {
1727 return NS_ERROR_OUT_OF_MEMORY;
1729 } else if (xmlHttpRequest->mResponseType ==
1730 XMLHttpRequestResponseType::_empty ||
1731 xmlHttpRequest->mResponseType ==
1732 XMLHttpRequestResponseType::Text ||
1733 xmlHttpRequest->mResponseType ==
1734 XMLHttpRequestResponseType::Json) {
1735 MOZ_ASSERT(!xmlHttpRequest->mResponseXML,
1736 "We shouldn't be parsing a doc here");
1737 rv = xmlHttpRequest->AppendToResponseText(
1738 AsBytes(Span(fromRawSegment, count)));
1739 if (NS_WARN_IF(NS_FAILED(rv))) {
1740 return rv;
1744 if (xmlHttpRequest->mFlagParseBody) {
1745 // Give the same data to the parser.
1747 // We need to wrap the data in a new lightweight stream and pass that
1748 // to the parser, because calling ReadSegments() recursively on the same
1749 // stream is not supported.
1750 nsCOMPtr<nsIInputStream> copyStream;
1751 rv = NS_NewByteInputStream(getter_AddRefs(copyStream),
1752 Span(fromRawSegment, count),
1753 NS_ASSIGNMENT_DEPEND);
1755 if (NS_SUCCEEDED(rv) && xmlHttpRequest->mXMLParserStreamListener) {
1756 NS_ASSERTION(copyStream, "NS_NewByteInputStream lied");
1757 nsresult parsingResult =
1758 xmlHttpRequest->mXMLParserStreamListener->OnDataAvailable(
1759 xmlHttpRequest->mChannel, copyStream, toOffset, count);
1761 // No use to continue parsing if we failed here, but we
1762 // should still finish reading the stream
1763 if (NS_FAILED(parsingResult)) {
1764 xmlHttpRequest->mFlagParseBody = false;
1769 if (NS_SUCCEEDED(rv)) {
1770 *writeCount = count;
1771 } else {
1772 *writeCount = 0;
1775 return rv;
1778 namespace {
1780 void GetBlobURIFromChannel(nsIRequest* aRequest, nsIURI** aURI) {
1781 MOZ_ASSERT(aRequest);
1782 MOZ_ASSERT(aURI);
1784 *aURI = nullptr;
1786 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
1787 if (!channel) {
1788 return;
1791 nsCOMPtr<nsIURI> uri;
1792 nsresult rv = channel->GetURI(getter_AddRefs(uri));
1793 if (NS_FAILED(rv)) {
1794 return;
1797 if (!dom::IsBlobURI(uri)) {
1798 return;
1801 uri.forget(aURI);
1804 nsresult GetLocalFileFromChannel(nsIRequest* aRequest, nsIFile** aFile) {
1805 MOZ_ASSERT(aRequest);
1806 MOZ_ASSERT(aFile);
1808 *aFile = nullptr;
1810 nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aRequest);
1811 if (!fc) {
1812 return NS_OK;
1815 nsCOMPtr<nsIFile> file;
1816 nsresult rv = fc->GetFile(getter_AddRefs(file));
1817 if (NS_WARN_IF(NS_FAILED(rv))) {
1818 return rv;
1821 file.forget(aFile);
1822 return NS_OK;
1825 nsresult DummyStreamReaderFunc(nsIInputStream* aInputStream, void* aClosure,
1826 const char* aFromRawSegment, uint32_t aToOffset,
1827 uint32_t aCount, uint32_t* aWriteCount) {
1828 *aWriteCount = aCount;
1829 return NS_OK;
1832 class FileCreationHandler final : public PromiseNativeHandler {
1833 public:
1834 NS_DECL_ISUPPORTS
1836 static void Create(Promise* aPromise, XMLHttpRequestMainThread* aXHR) {
1837 MOZ_ASSERT(aPromise);
1839 RefPtr<FileCreationHandler> handler = new FileCreationHandler(aXHR);
1840 aPromise->AppendNativeHandler(handler);
1843 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
1844 ErrorResult& aRv) override {
1845 if (NS_WARN_IF(!aValue.isObject())) {
1846 mXHR->LocalFileToBlobCompleted(nullptr);
1847 return;
1850 RefPtr<Blob> blob;
1851 if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) {
1852 mXHR->LocalFileToBlobCompleted(nullptr);
1853 return;
1856 mXHR->LocalFileToBlobCompleted(blob->Impl());
1859 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
1860 ErrorResult& aRv) override {
1861 mXHR->LocalFileToBlobCompleted(nullptr);
1864 private:
1865 explicit FileCreationHandler(XMLHttpRequestMainThread* aXHR) : mXHR(aXHR) {
1866 MOZ_ASSERT(aXHR);
1869 ~FileCreationHandler() = default;
1871 RefPtr<XMLHttpRequestMainThread> mXHR;
1874 NS_IMPL_ISUPPORTS0(FileCreationHandler)
1876 } // namespace
1878 void XMLHttpRequestMainThread::LocalFileToBlobCompleted(BlobImpl* aBlobImpl) {
1879 MOZ_ASSERT(mState != XMLHttpRequest_Binding::DONE);
1881 mResponseBlobImpl = aBlobImpl;
1882 mBlobStorage = nullptr;
1883 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1885 ChangeStateToDone(mFlagSyncLooping);
1888 NS_IMETHODIMP
1889 XMLHttpRequestMainThread::OnDataAvailable(nsIRequest* request,
1890 nsIInputStream* inStr,
1891 uint64_t sourceOffset,
1892 uint32_t count) {
1893 DEBUG_WORKERREFS;
1894 NS_ENSURE_ARG_POINTER(inStr);
1896 mProgressSinceLastProgressEvent = true;
1897 XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
1899 nsresult rv;
1901 if (mResponseType == XMLHttpRequestResponseType::Blob) {
1902 nsCOMPtr<nsIFile> localFile;
1903 nsCOMPtr<nsIURI> blobURI;
1904 GetBlobURIFromChannel(request, getter_AddRefs(blobURI));
1905 if (blobURI) {
1906 RefPtr<BlobImpl> blobImpl;
1907 rv = NS_GetBlobForBlobURI(blobURI, getter_AddRefs(blobImpl));
1908 if (NS_SUCCEEDED(rv)) {
1909 mResponseBlobImpl = blobImpl;
1911 } else {
1912 rv = GetLocalFileFromChannel(request, getter_AddRefs(localFile));
1914 if (NS_WARN_IF(NS_FAILED(rv))) {
1915 return rv;
1918 if (mResponseBlobImpl || localFile) {
1919 mBlobStorage = nullptr;
1920 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
1922 // The nsIStreamListener contract mandates us to read from the stream
1923 // before returning.
1924 uint32_t totalRead;
1925 rv = inStr->ReadSegments(DummyStreamReaderFunc, nullptr, count,
1926 &totalRead);
1927 NS_ENSURE_SUCCESS(rv, rv);
1929 ChangeState(XMLHttpRequest_Binding::LOADING);
1931 // Cancel() must be called with an error. We use
1932 // NS_ERROR_FILE_ALREADY_EXISTS to know that we've aborted the operation
1933 // just because we can retrieve the File from the channel directly.
1934 return request->Cancel(NS_ERROR_FILE_ALREADY_EXISTS);
1938 uint32_t totalRead;
1939 rv = inStr->ReadSegments(XMLHttpRequestMainThread::StreamReaderFunc,
1940 (void*)this, count, &totalRead);
1941 NS_ENSURE_SUCCESS(rv, rv);
1943 // Fire the first progress event/loading state change
1944 if (mState == XMLHttpRequest_Binding::HEADERS_RECEIVED) {
1945 ChangeState(XMLHttpRequest_Binding::LOADING);
1946 if (!mFlagSynchronous) {
1947 DispatchProgressEvent(this, Events::progress, mLoadTransferred,
1948 mLoadTotal);
1950 mProgressSinceLastProgressEvent = false;
1953 if (!mFlagSynchronous && !mProgressTimerIsActive) {
1954 StartProgressEventTimer();
1957 return NS_OK;
1960 NS_IMETHODIMP
1961 XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) {
1962 DEBUG_WORKERREFS;
1963 AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStartRequest", NETWORK);
1965 nsresult rv = NS_OK;
1967 if (request != mChannel) {
1968 // Can this still happen?
1969 return NS_OK;
1972 // Don't do anything if we have been aborted
1973 if (mState == XMLHttpRequest_Binding::UNSENT) {
1974 return NS_OK;
1977 // Don't do anything if we're in mid-abort, but let the request
1978 // know (this can happen due to race conditions in valid XHRs,
1979 // see bz1070763 for info).
1980 if (mFlagAborted) {
1981 return NS_BINDING_ABORTED;
1984 // Don't do anything if we have timed out.
1985 if (mFlagTimedOut) {
1986 return NS_OK;
1989 // If we were asked for a bad range on a blob URL, but we're async,
1990 // we should throw now in order to fire an error progress event.
1991 if (BadContentRangeRequested()) {
1992 return NS_ERROR_NET_PARTIAL_TRANSFER;
1995 nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
1996 NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
1998 nsresult status;
1999 request->GetStatus(&status);
2000 if (mErrorLoad == ErrorType::eOK && NS_FAILED(status)) {
2001 mErrorLoad = ErrorType::eRequest;
2002 mErrorLoadDetail = status;
2005 // Upload phase is now over. If we were uploading anything,
2006 // stop the timer and fire any final progress events.
2007 if (mUpload && !mUploadComplete && mErrorLoad == ErrorType::eOK &&
2008 !mFlagSynchronous) {
2009 StopProgressEventTimer();
2011 mUploadTransferred = mUploadTotal;
2013 if (mProgressSinceLastProgressEvent) {
2014 DispatchProgressEvent(mUpload, Events::progress, mUploadTransferred,
2015 mUploadTotal);
2016 mProgressSinceLastProgressEvent = false;
2019 mUploadComplete = true;
2020 DispatchProgressEvent(mUpload, Events::load, mUploadTotal, mUploadTotal);
2023 mFlagParseBody = true;
2024 if (mErrorLoad == ErrorType::eOK) {
2025 ChangeState(XMLHttpRequest_Binding::HEADERS_RECEIVED);
2028 ResetResponse();
2030 if (!mOverrideMimeType.IsEmpty()) {
2031 channel->SetContentType(NS_ConvertUTF16toUTF8(mOverrideMimeType));
2034 // Fallback to 'application/octet-stream' (leaving data URLs alone)
2035 if (!IsBlobURI(mRequestURL)) {
2036 nsAutoCString type;
2037 channel->GetContentType(type);
2038 if (type.IsEmpty() || type.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
2039 channel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
2043 DetectCharset();
2045 // Set up arraybuffer
2046 if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
2047 NS_SUCCEEDED(status)) {
2048 if (mIsMappedArrayBuffer) {
2049 nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
2050 if (jarChannel) {
2051 nsCOMPtr<nsIURI> uri;
2052 rv = channel->GetURI(getter_AddRefs(uri));
2053 if (NS_SUCCEEDED(rv)) {
2054 nsAutoCString file;
2055 nsAutoCString scheme;
2056 uri->GetScheme(scheme);
2057 if (scheme.LowerCaseEqualsLiteral("jar")) {
2058 nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri);
2059 if (jarURI) {
2060 jarURI->GetJAREntry(file);
2063 nsCOMPtr<nsIFile> jarFile;
2064 jarChannel->GetJarFile(getter_AddRefs(jarFile));
2065 if (!jarFile) {
2066 mIsMappedArrayBuffer = false;
2067 } else {
2068 rv = mArrayBufferBuilder->MapToFileInPackage(file, jarFile);
2069 // This can happen legitimately if there are compressed files
2070 // in the jarFile. See bug #1357219. No need to warn on the error.
2071 if (NS_FAILED(rv)) {
2072 mIsMappedArrayBuffer = false;
2073 } else {
2074 channel->SetContentType("application/mem-mapped"_ns);
2080 // If memory mapping failed, mIsMappedArrayBuffer would be set to false,
2081 // and we want it fallback to the malloc way.
2082 if (!mIsMappedArrayBuffer) {
2083 int64_t contentLength;
2084 rv = channel->GetContentLength(&contentLength);
2085 if (NS_SUCCEEDED(rv) && contentLength > 0 &&
2086 contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) {
2087 mArrayBufferBuilder->SetCapacity(static_cast<int32_t>(contentLength));
2092 // Set up responseXML
2093 // Fetch spec Main Fetch step 21: ignore body for head/connect methods.
2094 bool parseBody = (mResponseType == XMLHttpRequestResponseType::_empty ||
2095 mResponseType == XMLHttpRequestResponseType::Document) &&
2096 !(mRequestMethod.EqualsLiteral("HEAD") ||
2097 mRequestMethod.EqualsLiteral("CONNECT"));
2099 if (parseBody) {
2100 // Do not try to parse documents if content-length = 0
2101 int64_t contentLength;
2102 if (NS_SUCCEEDED(mChannel->GetContentLength(&contentLength)) &&
2103 contentLength == 0) {
2104 parseBody = false;
2108 mIsHtml = false;
2109 mWarnAboutSyncHtml = false;
2110 if (parseBody && NS_SUCCEEDED(status)) {
2111 // We can gain a huge performance win by not even trying to
2112 // parse non-XML data. This also protects us from the situation
2113 // where we have an XML document and sink, but HTML (or other)
2114 // parser, which can produce unreliable results.
2115 nsAutoCString type;
2116 channel->GetContentType(type);
2118 if ((mResponseType == XMLHttpRequestResponseType::Document) &&
2119 type.EqualsLiteral("text/html")) {
2120 // HTML parsing is only supported for responseType == "document" to
2121 // avoid running the parser and, worse, populating responseXML for
2122 // legacy users of XHR who use responseType == "" for retrieving the
2123 // responseText of text/html resources. This legacy case is so common
2124 // that it's not useful to emit a warning about it.
2125 if (mFlagSynchronous) {
2126 // We don't make cool new features available in the bad synchronous
2127 // mode. The synchronous mode is for legacy only.
2128 mWarnAboutSyncHtml = true;
2129 mFlagParseBody = false;
2130 } else {
2131 mIsHtml = true;
2133 } else if (!type.IsEmpty() && (!(type.EqualsLiteral("text/xml") ||
2134 type.EqualsLiteral("application/xml") ||
2135 StringEndsWith(type, "+xml"_ns)))) {
2136 // Follow https://xhr.spec.whatwg.org/
2137 // If final MIME type is not null, text/html, text/xml, application/xml,
2138 // or does not end in +xml, return null.
2139 mFlagParseBody = false;
2141 } else {
2142 // The request failed, so we shouldn't be parsing anyway
2143 mFlagParseBody = false;
2146 if (mFlagParseBody) {
2147 nsCOMPtr<nsIURI> baseURI, docURI;
2148 rv = mChannel->GetURI(getter_AddRefs(docURI));
2149 NS_ENSURE_SUCCESS(rv, rv);
2150 baseURI = docURI;
2152 nsCOMPtr<Document> doc = GetDocumentIfCurrent();
2153 nsCOMPtr<nsIURI> chromeXHRDocURI, chromeXHRDocBaseURI;
2154 if (doc) {
2155 chromeXHRDocURI = doc->GetDocumentURI();
2156 chromeXHRDocBaseURI = doc->GetBaseURI();
2157 } else {
2158 // If we're no longer current, just kill the load, though it really should
2159 // have been killed already.
2160 if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
2161 return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
2165 // Create an empty document from it.
2166 const auto& emptyStr = u""_ns;
2167 nsIGlobalObject* global = DOMEventTargetHelper::GetParentObject();
2169 nsCOMPtr<nsIPrincipal> requestingPrincipal;
2170 rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
2171 channel, getter_AddRefs(requestingPrincipal));
2172 NS_ENSURE_SUCCESS(rv, rv);
2174 rv = NS_NewDOMDocument(
2175 getter_AddRefs(mResponseXML), emptyStr, emptyStr, nullptr, docURI,
2176 baseURI, requestingPrincipal, true, global,
2177 mIsHtml ? DocumentFlavorHTML : DocumentFlavorLegacyGuess);
2178 NS_ENSURE_SUCCESS(rv, rv);
2179 mResponseXML->SetChromeXHRDocURI(chromeXHRDocURI);
2180 mResponseXML->SetChromeXHRDocBaseURI(chromeXHRDocBaseURI);
2182 // suppress parsing failure messages to console for statuses which
2183 // can have empty bodies (see bug 884693).
2184 IgnoredErrorResult rv2;
2185 uint32_t responseStatus = GetStatus(rv2);
2186 if (!rv2.Failed() && (responseStatus == 201 || responseStatus == 202 ||
2187 responseStatus == 204 || responseStatus == 205 ||
2188 responseStatus == 304)) {
2189 mResponseXML->SetSuppressParserErrorConsoleMessages(true);
2192 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2193 bool isCrossSite = false;
2194 isCrossSite = loadInfo->GetTainting() != LoadTainting::Basic;
2196 if (isCrossSite) {
2197 mResponseXML->DisableCookieAccess();
2200 nsCOMPtr<nsIStreamListener> listener;
2201 nsCOMPtr<nsILoadGroup> loadGroup;
2202 channel->GetLoadGroup(getter_AddRefs(loadGroup));
2204 // suppress <parsererror> nodes on XML document parse failure, but only
2205 // for non-privileged code (including Web Extensions). See bug 289714.
2206 if (!IsSystemXHR()) {
2207 mResponseXML->SetSuppressParserErrorElement(true);
2210 rv = mResponseXML->StartDocumentLoad(kLoadAsData, channel, loadGroup,
2211 nullptr, getter_AddRefs(listener),
2212 !isCrossSite);
2213 NS_ENSURE_SUCCESS(rv, rv);
2215 // the spec requires the response document.referrer to be the empty string
2216 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2217 new ReferrerInfo(nullptr, mResponseXML->ReferrerPolicy());
2218 mResponseXML->SetReferrerInfo(referrerInfo);
2220 mXMLParserStreamListener = listener;
2221 rv = mXMLParserStreamListener->OnStartRequest(request);
2222 NS_ENSURE_SUCCESS(rv, rv);
2225 // Download phase beginning; start the progress event timer if necessary.
2226 if (NS_SUCCEEDED(rv) && HasListenersFor(nsGkAtoms::onprogress)) {
2227 StartProgressEventTimer();
2230 return NS_OK;
2233 NS_IMETHODIMP
2234 XMLHttpRequestMainThread::OnStopRequest(nsIRequest* request, nsresult status) {
2235 DEBUG_WORKERREFS;
2236 AUTO_PROFILER_LABEL("XMLHttpRequestMainThread::OnStopRequest", NETWORK);
2238 if (request != mChannel) {
2239 // Can this still happen?
2240 return NS_OK;
2243 // Send the decoder the signal that we've hit the end of the stream,
2244 // but only when decoding text eagerly.
2245 if (mDecoder && ((mResponseType == XMLHttpRequestResponseType::Text) ||
2246 (mResponseType == XMLHttpRequestResponseType::Json) ||
2247 (mResponseType == XMLHttpRequestResponseType::_empty &&
2248 !mResponseXML))) {
2249 AppendToResponseText(Span<const uint8_t>(), true);
2252 mWaitingForOnStopRequest = false;
2254 // make sure to notify the listener if we were aborted
2255 // XXX in fact, why don't we do the cleanup below in this case??
2256 // UNSENT is for abort calls. See OnStartRequest above.
2257 if (mState == XMLHttpRequest_Binding::UNSENT || mFlagTimedOut) {
2258 if (mXMLParserStreamListener)
2259 (void)mXMLParserStreamListener->OnStopRequest(request, status);
2260 return NS_OK;
2263 // Is this good enough here?
2264 if (mXMLParserStreamListener && mFlagParseBody) {
2265 mXMLParserStreamListener->OnStopRequest(request, status);
2268 mXMLParserStreamListener = nullptr;
2269 mContext = nullptr;
2271 // If window.stop() or other aborts were issued, handle as an abort
2272 if (status == NS_BINDING_ABORTED) {
2273 mFlagParseBody = false;
2274 IgnoredErrorResult rv;
2275 RequestErrorSteps(Events::abort, NS_ERROR_DOM_ABORT_ERR, rv);
2276 ChangeState(XMLHttpRequest_Binding::UNSENT, false);
2277 return NS_OK;
2280 // If we were just reading a blob URL, we're already done
2281 if (status == NS_ERROR_FILE_ALREADY_EXISTS && mResponseBlobImpl) {
2282 ChangeStateToDone(mFlagSyncLooping);
2283 return NS_OK;
2286 bool waitingForBlobCreation = false;
2288 // If we have this error, we have to deal with a file: URL + responseType =
2289 // blob. We have this error because we canceled the channel. The status will
2290 // be set to NS_OK.
2291 if (!mResponseBlobImpl && status == NS_ERROR_FILE_ALREADY_EXISTS &&
2292 mResponseType == XMLHttpRequestResponseType::Blob) {
2293 nsCOMPtr<nsIFile> file;
2294 nsresult rv = GetLocalFileFromChannel(request, getter_AddRefs(file));
2295 if (NS_WARN_IF(NS_FAILED(rv))) {
2296 return rv;
2299 if (file) {
2300 nsAutoCString contentType;
2301 rv = mChannel->GetContentType(contentType);
2302 if (NS_WARN_IF(NS_FAILED(rv))) {
2303 return rv;
2306 ChromeFilePropertyBag bag;
2307 CopyUTF8toUTF16(contentType, bag.mType);
2309 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
2311 ErrorResult error;
2312 RefPtr<Promise> promise =
2313 FileCreatorHelper::CreateFile(global, file, bag, true, error);
2314 if (NS_WARN_IF(error.Failed())) {
2315 return error.StealNSResult();
2318 FileCreationHandler::Create(promise, this);
2319 waitingForBlobCreation = true;
2320 status = NS_OK;
2322 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
2323 NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
2327 if (NS_SUCCEEDED(status) &&
2328 mResponseType == XMLHttpRequestResponseType::Blob &&
2329 !waitingForBlobCreation) {
2330 // Smaller files may be written in cache map instead of separate files.
2331 // Also, no-store response cannot be written in persistent cache.
2332 nsAutoCString contentType;
2333 if (!mOverrideMimeType.IsEmpty()) {
2334 contentType.Assign(NS_ConvertUTF16toUTF8(mOverrideMimeType));
2335 } else {
2336 mChannel->GetContentType(contentType);
2339 // mBlobStorage can be null if the channel is non-file non-cacheable
2340 // and if the response length is zero.
2341 MaybeCreateBlobStorage();
2342 mBlobStorage->GetBlobImplWhenReady(contentType, this);
2343 waitingForBlobCreation = true;
2345 NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
2346 NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
2347 } else if (NS_SUCCEEDED(status) && !mIsMappedArrayBuffer &&
2348 mResponseType == XMLHttpRequestResponseType::Arraybuffer) {
2349 // set the capacity down to the actual length, to realloc back
2350 // down to the actual size
2351 if (!mArrayBufferBuilder->SetCapacity(mArrayBufferBuilder->Length())) {
2352 // this should never happen!
2353 status = NS_ERROR_UNEXPECTED;
2357 nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
2358 NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
2360 channel->SetNotificationCallbacks(nullptr);
2361 mNotificationCallbacks = nullptr;
2362 mChannelEventSink = nullptr;
2363 mProgressEventSink = nullptr;
2365 bool wasSync = mFlagSyncLooping;
2366 mFlagSyncLooping = false;
2367 mRequestSentTime = 0;
2369 // update our charset and decoder to match mResponseXML,
2370 // before it is possibly nulled out
2371 MatchCharsetAndDecoderToResponseDocument();
2373 if (NS_FAILED(status)) {
2374 // This can happen if the server is unreachable. Other possible
2375 // reasons are that the user leaves the page or hits the ESC key.
2377 mErrorLoad = ErrorType::eUnreachable;
2378 mErrorLoadDetail = status;
2379 mResponseXML = nullptr;
2381 // Handle network errors specifically per spec.
2382 if (NS_ERROR_GET_MODULE(status) == NS_ERROR_MODULE_NETWORK) {
2383 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
2384 ("%p detected networking error 0x%" PRIx32 "\n", this,
2385 static_cast<uint32_t>(status)));
2386 IgnoredErrorResult rv;
2387 mFlagParseBody = false;
2388 RequestErrorSteps(Events::error, NS_ERROR_DOM_NETWORK_ERR, rv);
2389 // RequestErrorSteps will not call ChangeStateToDone for sync XHRs, so we
2390 // do so here to ensure progress events are sent and our state is sane.
2391 if (mFlagSynchronous) {
2392 ChangeStateToDone(wasSync);
2394 return NS_OK;
2397 MOZ_LOG(gXMLHttpRequestLog, LogLevel::Debug,
2398 ("%p detected unreachable error 0x%" PRIx32 "\n", this,
2399 static_cast<uint32_t>(status)));
2402 // If we're uninitialized at this point, we encountered an error
2403 // earlier and listeners have already been notified. Also we do
2404 // not want to do this if we already completed.
2405 if (mState == XMLHttpRequest_Binding::UNSENT ||
2406 mState == XMLHttpRequest_Binding::DONE) {
2407 return NS_OK;
2410 if (!mResponseXML) {
2411 mFlagParseBody = false;
2413 // We postpone the 'done' until the creation of the Blob is completed.
2414 if (!waitingForBlobCreation) {
2415 ChangeStateToDone(wasSync);
2418 return NS_OK;
2421 if (mIsHtml) {
2422 NS_ASSERTION(!mFlagSyncLooping,
2423 "We weren't supposed to support HTML parsing with XHR!");
2424 mParseEndListener = new nsXHRParseEndListener(this);
2425 RefPtr<EventTarget> eventTarget = mResponseXML;
2426 EventListenerManager* manager = eventTarget->GetOrCreateListenerManager();
2427 manager->AddEventListenerByType(mParseEndListener,
2428 kLiteralString_DOMContentLoaded,
2429 TrustedEventsAtSystemGroupBubble());
2430 return NS_OK;
2431 } else {
2432 mFlagParseBody = false;
2435 // We might have been sent non-XML data. If that was the case,
2436 // we should null out the document member. The idea in this
2437 // check here is that if there is no document element it is not
2438 // an XML document. We might need a fancier check...
2439 if (!mResponseXML->GetRootElement()) {
2440 mErrorParsingXML = true;
2441 mResponseXML = nullptr;
2443 ChangeStateToDone(wasSync);
2444 return NS_OK;
2447 void XMLHttpRequestMainThread::OnBodyParseEnd() {
2448 mFlagParseBody = false;
2449 mParseEndListener = nullptr;
2450 ChangeStateToDone(mFlagSyncLooping);
2453 void XMLHttpRequestMainThread::MatchCharsetAndDecoderToResponseDocument() {
2454 if (mResponseXML &&
2455 (!mDecoder ||
2456 mDecoder->Encoding() != mResponseXML->GetDocumentCharacterSet())) {
2457 TruncateResponseText();
2458 mResponseBodyDecodedPos = 0;
2459 mEofDecoded = false;
2460 mDecoder = mResponseXML->GetDocumentCharacterSet()->NewDecoder();
2464 void XMLHttpRequestMainThread::DisconnectDoneNotifier() {
2465 if (mDelayedDoneNotifier) {
2466 // Disconnect may release the last reference to 'this'.
2467 RefPtr<XMLHttpRequestMainThread> kungfuDeathGrip = this;
2468 mDelayedDoneNotifier->Disconnect();
2469 mDelayedDoneNotifier = nullptr;
2473 void XMLHttpRequestMainThread::ChangeStateToDone(bool aWasSync) {
2474 DEBUG_WORKERREFS;
2475 DisconnectDoneNotifier();
2477 if (!mForWorker && !aWasSync && mChannel) {
2478 // If the top level page is loading, try to postpone the handling of the
2479 // final events.
2480 nsLoadFlags loadFlags = 0;
2481 mChannel->GetLoadFlags(&loadFlags);
2482 if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
2483 nsPIDOMWindowInner* owner = GetOwner();
2484 BrowsingContext* bc = owner ? owner->GetBrowsingContext() : nullptr;
2485 bc = bc ? bc->Top() : nullptr;
2486 if (bc && bc->IsLoading()) {
2487 MOZ_ASSERT(!mDelayedDoneNotifier);
2488 RefPtr<XMLHttpRequestDoneNotifier> notifier =
2489 new XMLHttpRequestDoneNotifier(this);
2490 mDelayedDoneNotifier = notifier;
2491 bc->AddDeprioritizedLoadRunner(notifier);
2492 return;
2497 ChangeStateToDoneInternal();
2500 void XMLHttpRequestMainThread::ChangeStateToDoneInternal() {
2501 DEBUG_WORKERREFS;
2502 DisconnectDoneNotifier();
2503 StopProgressEventTimer();
2505 MOZ_ASSERT(!mFlagParseBody,
2506 "ChangeStateToDone() called before async HTML parsing is done.");
2508 mFlagSend = false;
2510 CancelTimeoutTimer();
2512 // Per spec, fire the last download progress event, if any,
2513 // before readystatechange=4/done. (Note that 0-sized responses
2514 // will have not sent a progress event yet, so one must be sent here).
2515 if (!mFlagSynchronous &&
2516 (!mLoadTransferred || mProgressSinceLastProgressEvent)) {
2517 DispatchProgressEvent(this, Events::progress, mLoadTransferred, mLoadTotal);
2518 mProgressSinceLastProgressEvent = false;
2521 // Notify the document when an XHR request completes successfully.
2522 // This is used by the password manager as a hint to observe DOM mutations.
2523 // Call this prior to changing state to DONE to ensure we set up the
2524 // observer before mutations occur.
2525 if (mErrorLoad == ErrorType::eOK) {
2526 Document* doc = GetDocumentIfCurrent();
2527 if (doc) {
2528 doc->NotifyFetchOrXHRSuccess();
2532 // Per spec, fire readystatechange=4/done before final error events.
2533 ChangeState(XMLHttpRequest_Binding::DONE, true);
2535 // Per spec, if we failed in the upload phase, fire a final error
2536 // and loadend events for the upload after readystatechange=4/done.
2537 if (!mFlagSynchronous && mUpload && !mUploadComplete) {
2538 DispatchProgressEvent(mUpload, Events::error, 0, -1);
2541 // Per spec, fire download's load/error and loadend events after
2542 // readystatechange=4/done (and of course all upload events).
2543 if (mErrorLoad != ErrorType::eOK) {
2544 DispatchProgressEvent(this, Events::error, 0, -1);
2545 } else {
2546 DispatchProgressEvent(this, Events::load, mLoadTransferred, mLoadTotal);
2549 if (mErrorLoad != ErrorType::eOK) {
2550 // By nulling out channel here we make it so that Send() can test
2551 // for that and throw. Also calling the various status
2552 // methods/members will not throw.
2553 // This matches what IE does.
2554 mChannel = nullptr;
2558 nsresult XMLHttpRequestMainThread::CreateChannel() {
2559 DEBUG_WORKERREFS;
2560 // When we are called from JS we can find the load group for the page,
2561 // and add ourselves to it. This way any pending requests
2562 // will be automatically aborted if the user leaves the page.
2563 nsCOMPtr<nsILoadGroup> loadGroup = GetLoadGroup();
2565 nsSecurityFlags secFlags;
2566 nsLoadFlags loadFlags = nsIRequest::LOAD_BACKGROUND;
2567 uint32_t sandboxFlags = 0;
2568 if (mPrincipal->IsSystemPrincipal()) {
2569 // When chrome is loading we want to make sure to sandbox any potential
2570 // result document. We also want to allow cross-origin loads.
2571 secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
2572 sandboxFlags = SANDBOXED_ORIGIN;
2573 } else if (IsSystemXHR()) {
2574 // For pages that have appropriate permissions, we want to still allow
2575 // cross-origin loads, but make sure that the any potential result
2576 // documents get the same principal as the loader.
2577 secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
2578 nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
2579 loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
2580 } else {
2581 // Otherwise use CORS. Again, make sure that potential result documents
2582 // use the same principal as the loader.
2583 secFlags = nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT |
2584 nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
2587 if (mIsAnon) {
2588 secFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
2591 // Use the responsibleDocument if we have it, except for dedicated workers
2592 // where it will be the parent document, which is not the one we want to use.
2593 nsresult rv;
2594 nsCOMPtr<Document> responsibleDocument = GetDocumentIfCurrent();
2595 if (responsibleDocument &&
2596 responsibleDocument->NodePrincipal() == mPrincipal) {
2597 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL,
2598 responsibleDocument, secFlags,
2599 nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2600 nullptr, // aPerformanceStorage
2601 loadGroup,
2602 nullptr, // aCallbacks
2603 loadFlags, nullptr, sandboxFlags);
2604 } else if (mClientInfo.isSome()) {
2605 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
2606 mClientInfo.ref(), mController, secFlags,
2607 nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2608 mCookieJarSettings,
2609 mPerformanceStorage, // aPerformanceStorage
2610 loadGroup,
2611 nullptr, // aCallbacks
2612 loadFlags, nullptr, sandboxFlags);
2613 } else {
2614 // Otherwise use the principal.
2615 rv = NS_NewChannel(getter_AddRefs(mChannel), mRequestURL, mPrincipal,
2616 secFlags, nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
2617 mCookieJarSettings,
2618 mPerformanceStorage, // aPerformanceStorage
2619 loadGroup,
2620 nullptr, // aCallbacks
2621 loadFlags, nullptr, sandboxFlags);
2623 NS_ENSURE_SUCCESS(rv, rv);
2625 if (mCSPEventListener) {
2626 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2627 rv = loadInfo->SetCspEventListener(mCSPEventListener);
2628 NS_ENSURE_SUCCESS(rv, rv);
2631 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2632 if (httpChannel) {
2633 rv = httpChannel->SetRequestMethod(mRequestMethod);
2634 NS_ENSURE_SUCCESS(rv, rv);
2636 httpChannel->SetSource(profiler_capture_backtrace());
2638 // Set the initiator type
2639 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
2640 if (timedChannel) {
2641 timedChannel->SetInitiatorType(u"xmlhttprequest"_ns);
2645 return NS_OK;
2648 void XMLHttpRequestMainThread::MaybeLowerChannelPriority() {
2649 nsCOMPtr<Document> doc = GetDocumentIfCurrent();
2650 if (!doc) {
2651 return;
2654 AutoJSAPI jsapi;
2655 if (!jsapi.Init(GetOwnerGlobal())) {
2656 return;
2659 JSContext* cx = jsapi.cx();
2661 if (!doc->IsScriptTracking(cx)) {
2662 return;
2665 if (StaticPrefs::network_http_tailing_enabled()) {
2666 nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel);
2667 if (cos) {
2668 // Adding TailAllowed to overrule the Unblocked flag, but to preserve
2669 // the effect of Unblocked when tailing is off.
2670 cos->AddClassFlags(nsIClassOfService::Throttleable |
2671 nsIClassOfService::Tail |
2672 nsIClassOfService::TailAllowed);
2676 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
2677 if (p) {
2678 p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
2682 nsresult XMLHttpRequestMainThread::InitiateFetch(
2683 already_AddRefed<nsIInputStream> aUploadStream, int64_t aUploadLength,
2684 nsACString& aUploadContentType) {
2685 DEBUG_WORKERREFS;
2686 nsresult rv;
2687 nsCOMPtr<nsIInputStream> uploadStream = std::move(aUploadStream);
2689 if (!uploadStream) {
2690 RefPtr<PreloaderBase> preload = FindPreload();
2691 if (preload) {
2692 // Because of bug 682305, we can't let listener be the XHR object itself
2693 // because JS wouldn't be able to use it. So create a listener around
2694 // 'this'. Make sure to hold a strong reference so that we don't leak the
2695 // wrapper.
2696 nsCOMPtr<nsIStreamListener> listener =
2697 new net::nsStreamListenerWrapper(this);
2698 rv = preload->AsyncConsume(listener);
2699 if (NS_SUCCEEDED(rv)) {
2700 mFromPreload = true;
2702 // May be null when the preload has already finished, but the XHR code
2703 // is safe to live with it.
2704 mChannel = preload->Channel();
2705 MOZ_ASSERT(mChannel);
2706 EnsureChannelContentType();
2707 return NS_OK;
2710 preload = nullptr;
2714 // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which
2715 // in turn keeps STOP button from becoming active. If the consumer passed in
2716 // a progress event handler we must load with nsIRequest::LOAD_NORMAL or
2717 // necko won't generate any progress notifications.
2718 if (HasListenersFor(nsGkAtoms::onprogress) ||
2719 (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress))) {
2720 nsLoadFlags loadFlags;
2721 mChannel->GetLoadFlags(&loadFlags);
2722 loadFlags &= ~nsIRequest::LOAD_BACKGROUND;
2723 loadFlags |= nsIRequest::LOAD_NORMAL;
2724 mChannel->SetLoadFlags(loadFlags);
2727 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
2728 if (httpChannel) {
2729 // If the user hasn't overridden the Accept header, set it to */* per spec.
2730 if (!mAuthorRequestHeaders.Has("accept")) {
2731 mAuthorRequestHeaders.Set("accept", "*/*"_ns);
2734 mAuthorRequestHeaders.ApplyToChannel(httpChannel, false, false);
2736 if (!IsSystemXHR()) {
2737 nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
2738 nsCOMPtr<Document> doc = owner ? owner->GetExtantDoc() : nullptr;
2739 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2740 ReferrerInfo::CreateForFetch(mPrincipal, doc);
2741 Unused << httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
2744 // Some extensions override the http protocol handler and provide their own
2745 // implementation. The channels returned from that implementation don't
2746 // always seem to implement the nsIUploadChannel2 interface, presumably
2747 // because it's a new interface. Eventually we should remove this and simply
2748 // require that http channels implement the new interface (see bug 529041).
2749 nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
2750 if (!uploadChannel2) {
2751 nsCOMPtr<nsIConsoleService> consoleService =
2752 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
2753 if (consoleService) {
2754 consoleService->LogStringMessage(
2755 u"Http channel implementation doesn't support nsIUploadChannel2. "
2756 "An extension has supplied a non-functional http protocol handler. "
2757 "This will break behavior and in future releases not work at all.");
2761 if (uploadStream) {
2762 // If necessary, wrap the stream in a buffered stream so as to guarantee
2763 // support for our upload when calling ExplicitSetUploadStream.
2764 if (!NS_InputStreamIsBuffered(uploadStream)) {
2765 nsCOMPtr<nsIInputStream> bufferedStream;
2766 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
2767 uploadStream.forget(), 4096);
2768 NS_ENSURE_SUCCESS(rv, rv);
2770 uploadStream = bufferedStream;
2773 // We want to use a newer version of the upload channel that won't
2774 // ignore the necessary headers for an empty Content-Type.
2775 nsCOMPtr<nsIUploadChannel2> uploadChannel2(
2776 do_QueryInterface(httpChannel));
2777 // This assertion will fire if buggy extensions are installed
2778 NS_ASSERTION(uploadChannel2, "http must support nsIUploadChannel2");
2779 if (uploadChannel2) {
2780 uploadChannel2->ExplicitSetUploadStream(
2781 uploadStream, aUploadContentType, mUploadTotal, mRequestMethod,
2782 false);
2783 } else {
2784 // The http channel doesn't support the new nsIUploadChannel2.
2785 // Emulate it as best we can using nsIUploadChannel.
2786 if (aUploadContentType.IsEmpty()) {
2787 aUploadContentType.AssignLiteral("application/octet-stream");
2789 nsCOMPtr<nsIUploadChannel> uploadChannel =
2790 do_QueryInterface(httpChannel);
2791 uploadChannel->SetUploadStream(uploadStream, aUploadContentType,
2792 mUploadTotal);
2793 // Reset the method to its original value
2794 rv = httpChannel->SetRequestMethod(mRequestMethod);
2795 MOZ_ASSERT(NS_SUCCEEDED(rv));
2800 // Should set a Content-Range header for blob scheme, and also slice the
2801 // blob appropriately, so we process the Range header here for later use.
2802 if (IsBlobURI(mRequestURL)) {
2803 nsAutoCString range;
2804 mAuthorRequestHeaders.Get("range", range);
2805 if (!range.IsVoid()) {
2806 rv = NS_SetChannelContentRangeForBlobURI(mChannel, mRequestURL, range);
2807 if (mFlagSynchronous && NS_FAILED(rv)) {
2808 // We later fire an error progress event for non-sync
2809 mState = XMLHttpRequest_Binding::DONE;
2810 return NS_ERROR_DOM_NETWORK_ERR;
2815 // Due to the chrome-only XHR.channel API, we need a hacky way to set the
2816 // SEC_COOKIES_INCLUDE *after* the channel has been has been created, since
2817 // .withCredentials can be called after open() is called.
2818 // Not doing this for privileged system XHRs since those don't use CORS.
2819 if (!IsSystemXHR() && !mIsAnon && mFlagACwithCredentials) {
2820 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2821 static_cast<net::LoadInfo*>(loadInfo.get())->SetIncludeCookiesSecFlag();
2824 // We never let XHR be blocked by head CSS/JS loads to avoid potential
2825 // deadlock where server generation of CSS/JS requires an XHR signal.
2826 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
2827 if (cos) {
2828 cos->AddClassFlags(nsIClassOfService::Unblocked);
2830 // Mark channel as urgent-start if the XHR is triggered by user input
2831 // events.
2832 if (UserActivation::IsHandlingUserInput()) {
2833 cos->AddClassFlags(nsIClassOfService::UrgentStart);
2837 // Disable Necko-internal response timeouts.
2838 nsCOMPtr<nsIHttpChannelInternal> internalHttpChannel(
2839 do_QueryInterface(mChannel));
2840 if (internalHttpChannel) {
2841 rv = internalHttpChannel->SetResponseTimeoutEnabled(false);
2842 MOZ_ASSERT(NS_SUCCEEDED(rv));
2845 if (!mIsAnon) {
2846 AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS);
2849 // Bypass the network cache in cases where it makes no sense:
2850 // POST responses are always unique, and we provide no API that would
2851 // allow our consumers to specify a "cache key" to access old POST
2852 // responses, so they are not worth caching.
2853 if (mRequestMethod.EqualsLiteral("POST")) {
2854 AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE |
2855 nsIRequest::INHIBIT_CACHING);
2856 } else {
2857 // When we are sync loading, we need to bypass the local cache when it would
2858 // otherwise block us waiting for exclusive access to the cache. If we
2859 // don't do this, then we could dead lock in some cases (see bug 309424).
2861 // Also don't block on the cache entry on async if it is busy - favoring
2862 // parallelism over cache hit rate for xhr. This does not disable the cache
2863 // everywhere - only in cases where more than one channel for the same URI
2864 // is accessed simultanously.
2865 AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
2868 EnsureChannelContentType();
2870 // Set up the preflight if needed
2871 if (!IsSystemXHR()) {
2872 nsTArray<nsCString> CORSUnsafeHeaders;
2873 mAuthorRequestHeaders.GetCORSUnsafeHeaders(CORSUnsafeHeaders);
2874 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2875 loadInfo->SetCorsPreflightInfo(CORSUnsafeHeaders,
2876 mFlagHadUploadListenersOnSend);
2879 // Hook us up to listen to redirects and the like. Only do this very late
2880 // since this creates a cycle between the channel and us. This cycle has
2881 // to be manually broken if anything below fails.
2882 mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
2883 mChannel->SetNotificationCallbacks(this);
2885 if (internalHttpChannel) {
2886 internalHttpChannel->SetBlockAuthPrompt(ShouldBlockAuthPrompt());
2889 // Because of bug 682305, we can't let listener be the XHR object itself
2890 // because JS wouldn't be able to use it. So create a listener around 'this'.
2891 // Make sure to hold a strong reference so that we don't leak the wrapper.
2892 nsCOMPtr<nsIStreamListener> listener = new net::nsStreamListenerWrapper(this);
2894 // Check if this XHR is created from a tracking script.
2895 // If yes, lower the channel's priority.
2896 if (StaticPrefs::privacy_trackingprotection_lower_network_priority()) {
2897 MaybeLowerChannelPriority();
2900 // Associate any originating stack with the channel.
2901 NotifyNetworkMonitorAlternateStack(mChannel, std::move(mOriginStack));
2903 // Start reading from the channel
2904 rv = mChannel->AsyncOpen(listener);
2905 listener = nullptr;
2906 if (NS_WARN_IF(NS_FAILED(rv))) {
2907 // Drop our ref to the channel to avoid cycles. Also drop channel's
2908 // ref to us to be extra safe.
2909 mChannel->SetNotificationCallbacks(mNotificationCallbacks);
2910 mChannel = nullptr;
2912 mErrorLoad = ErrorType::eChannelOpen;
2913 mErrorLoadDetail = rv;
2915 // Per spec, we throw on sync errors, but not async.
2916 if (mFlagSynchronous) {
2917 mState = XMLHttpRequest_Binding::DONE;
2918 return NS_ERROR_DOM_NETWORK_ERR;
2922 return NS_OK;
2925 already_AddRefed<PreloaderBase> XMLHttpRequestMainThread::FindPreload() {
2926 Document* doc = GetDocumentIfCurrent();
2927 if (!doc) {
2928 return nullptr;
2930 if (mPrincipal->IsSystemPrincipal() || IsSystemXHR()) {
2931 return nullptr;
2933 if (!mRequestMethod.EqualsLiteral("GET")) {
2934 // Preload can only do GET.
2935 return nullptr;
2937 if (!mAuthorRequestHeaders.IsEmpty()) {
2938 // Preload can't set headers.
2939 return nullptr;
2942 // mIsAnon overrules mFlagACwithCredentials.
2943 CORSMode cors = (mIsAnon || !mFlagACwithCredentials)
2944 ? CORSMode::CORS_ANONYMOUS
2945 : CORSMode::CORS_USE_CREDENTIALS;
2946 nsCOMPtr<nsIReferrerInfo> referrerInfo =
2947 ReferrerInfo::CreateForFetch(mPrincipal, doc);
2948 auto key = PreloadHashKey::CreateAsFetch(mRequestURL, cors);
2949 RefPtr<PreloaderBase> preload = doc->Preloads().LookupPreload(key);
2950 if (!preload) {
2951 return nullptr;
2954 preload->RemoveSelf(doc);
2955 preload->NotifyUsage(doc, PreloaderBase::LoadBackground::Keep);
2957 return preload.forget();
2960 void XMLHttpRequestMainThread::EnsureChannelContentType() {
2961 MOZ_ASSERT(mChannel);
2963 // We don't mess with the content type of a blob URL.
2964 if (IsBlobURI(mRequestURL)) {
2965 return;
2968 // Since we expect XML data, set the type hint accordingly
2969 // if the channel doesn't know any content type.
2970 // This means that we always try to parse local files as XML
2971 // ignoring return value, as this is not critical. Use text/xml as fallback
2972 // MIME type.
2973 nsAutoCString contentType;
2974 if (NS_FAILED(mChannel->GetContentType(contentType)) ||
2975 contentType.IsEmpty() ||
2976 contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
2977 mChannel->SetContentType("text/xml"_ns);
2981 void XMLHttpRequestMainThread::ResumeTimeout() {
2982 DEBUG_WORKERREFS;
2983 MOZ_ASSERT(NS_IsMainThread());
2984 MOZ_ASSERT(mFlagSynchronous);
2986 if (mResumeTimeoutRunnable) {
2987 DispatchToMainThread(mResumeTimeoutRunnable.forget());
2988 mResumeTimeoutRunnable = nullptr;
2992 void XMLHttpRequestMainThread::Send(
2993 const Nullable<
2994 DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString>&
2995 aData,
2996 ErrorResult& aRv) {
2997 DEBUG_WORKERREFS1(mRequestURL);
2998 NOT_CALLABLE_IN_SYNC_SEND_RV
3000 if (!CanSend(aRv)) {
3001 return;
3004 if (aData.IsNull()) {
3005 SendInternal(nullptr, false, aRv);
3006 return;
3009 if (aData.Value().IsDocument()) {
3010 BodyExtractor<Document> body(&aData.Value().GetAsDocument());
3011 SendInternal(&body, true, aRv);
3012 return;
3015 if (aData.Value().IsBlob()) {
3016 BodyExtractor<const Blob> body(&aData.Value().GetAsBlob());
3017 SendInternal(&body, false, aRv);
3018 return;
3021 if (aData.Value().IsArrayBuffer()) {
3022 BodyExtractor<const ArrayBuffer> body(&aData.Value().GetAsArrayBuffer());
3023 SendInternal(&body, false, aRv);
3024 return;
3027 if (aData.Value().IsArrayBufferView()) {
3028 BodyExtractor<const ArrayBufferView> body(
3029 &aData.Value().GetAsArrayBufferView());
3030 SendInternal(&body, false, aRv);
3031 return;
3034 if (aData.Value().IsFormData()) {
3035 BodyExtractor<const FormData> body(&aData.Value().GetAsFormData());
3036 SendInternal(&body, false, aRv);
3037 return;
3040 if (aData.Value().IsURLSearchParams()) {
3041 BodyExtractor<const URLSearchParams> body(
3042 &aData.Value().GetAsURLSearchParams());
3043 SendInternal(&body, false, aRv);
3044 return;
3047 if (aData.Value().IsUSVString()) {
3048 BodyExtractor<const nsAString> body(&aData.Value().GetAsUSVString());
3049 SendInternal(&body, true, aRv);
3050 return;
3054 nsresult XMLHttpRequestMainThread::MaybeSilentSendFailure(nsresult aRv) {
3055 // Per spec, silently fail on async request failures; throw for sync.
3056 if (mFlagSynchronous) {
3057 mState = XMLHttpRequest_Binding::DONE;
3058 return NS_ERROR_DOM_NETWORK_ERR;
3061 // Defer the actual sending of async events just in case listeners
3062 // are attached after the send() method is called.
3063 Unused << NS_WARN_IF(
3064 NS_FAILED(DispatchToMainThread(NewRunnableMethod<ErrorProgressEventType>(
3065 "dom::XMLHttpRequestMainThread::CloseRequestWithError", this,
3066 &XMLHttpRequestMainThread::CloseRequestWithError, Events::error))));
3067 return NS_OK;
3070 bool XMLHttpRequestMainThread::CanSend(ErrorResult& aRv) {
3071 if (!mPrincipal) {
3072 aRv.Throw(NS_ERROR_NOT_INITIALIZED);
3073 return false;
3076 // Step 1
3077 if (mState != XMLHttpRequest_Binding::OPENED) {
3078 aRv.ThrowInvalidStateError("XMLHttpRequest state must be OPENED.");
3079 return false;
3082 // Step 2
3083 if (mFlagSend) {
3084 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3085 return false;
3088 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
3089 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT);
3090 return false;
3093 return true;
3096 void XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody,
3097 bool aBodyIsDocumentOrString,
3098 ErrorResult& aRv) {
3099 DEBUG_WORKERREFS;
3100 MOZ_ASSERT(NS_IsMainThread());
3102 // We expect that CanSend has been called before we get here!
3103 // We cannot move the remaining two checks below there because
3104 // MaybeSilentSendFailure can cause unexpected side effects if called
3105 // too early.
3107 // If open() failed to create the channel, then throw a network error
3108 // as per spec. We really should create the channel here in send(), but
3109 // we have internal code relying on the channel being created in open().
3110 if (!mChannel) {
3111 mErrorLoad = ErrorType::eChannelOpen;
3112 mErrorLoadDetail = NS_ERROR_DOM_NETWORK_ERR;
3113 mFlagSend = true; // so CloseRequestWithError sets us to DONE.
3114 aRv = MaybeSilentSendFailure(mErrorLoadDetail);
3115 return;
3118 // non-GET requests aren't allowed for blob.
3119 if (IsBlobURI(mRequestURL) && !mRequestMethod.EqualsLiteral("GET")) {
3120 mErrorLoad = ErrorType::eChannelOpen;
3121 mErrorLoadDetail = NS_ERROR_DOM_NETWORK_ERR;
3122 mFlagSend = true; // so CloseRequestWithError sets us to DONE.
3123 aRv = MaybeSilentSendFailure(mErrorLoadDetail);
3124 return;
3127 // XXX We should probably send a warning to the JS console
3128 // if there are no event listeners set and we are doing
3129 // an asynchronous call.
3131 mUploadTransferred = 0;
3132 mUploadTotal = 0;
3133 // By default we don't have any upload, so mark upload complete.
3134 mUploadComplete = true;
3135 mErrorLoad = ErrorType::eOK;
3136 mErrorLoadDetail = NS_OK;
3137 mLoadTotal = -1;
3138 nsCOMPtr<nsIInputStream> uploadStream;
3139 nsAutoCString uploadContentType;
3140 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
3141 if (aBody && httpChannel && !mRequestMethod.EqualsLiteral("GET") &&
3142 !mRequestMethod.EqualsLiteral("HEAD")) {
3143 nsAutoCString charset;
3144 nsAutoCString defaultContentType;
3145 uint64_t size_u64;
3146 aRv = aBody->GetAsStream(getter_AddRefs(uploadStream), &size_u64,
3147 defaultContentType, charset);
3148 if (aRv.Failed()) {
3149 return;
3152 // make sure it fits within js MAX_SAFE_INTEGER
3153 mUploadTotal =
3154 net::InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1;
3156 if (uploadStream) {
3157 // If author set no Content-Type, use the default from GetAsStream().
3158 mAuthorRequestHeaders.Get("content-type", uploadContentType);
3159 if (uploadContentType.IsVoid()) {
3160 uploadContentType = defaultContentType;
3161 } else if (aBodyIsDocumentOrString) {
3162 RefPtr<CMimeType> contentTypeRecord =
3163 CMimeType::Parse(uploadContentType);
3164 nsAutoCString charset;
3165 if (contentTypeRecord &&
3166 contentTypeRecord->GetParameterValue(kLiteralString_charset,
3167 charset) &&
3168 !charset.EqualsIgnoreCase("utf-8")) {
3169 contentTypeRecord->SetParameterValue(kLiteralString_charset,
3170 kLiteralString_UTF_8);
3171 contentTypeRecord->Serialize(uploadContentType);
3173 } else if (!charset.IsEmpty()) {
3174 // We don't want to set a charset for streams.
3175 // Replace all case-insensitive matches of the charset in the
3176 // content-type with the correct case.
3177 RequestHeaders::CharsetIterator iter(uploadContentType);
3178 while (iter.Next()) {
3179 if (!iter.Equals(charset, nsCaseInsensitiveCStringComparator)) {
3180 iter.Replace(charset);
3185 mUploadComplete = false;
3189 ResetResponse();
3191 // Check if we should enable cross-origin upload listeners.
3192 if (mUpload && mUpload->HasListeners()) {
3193 mFlagHadUploadListenersOnSend = true;
3196 mIsMappedArrayBuffer = false;
3197 if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
3198 StaticPrefs::dom_mapped_arraybuffer_enabled()) {
3199 nsCOMPtr<nsIURI> uri;
3200 nsAutoCString scheme;
3202 aRv = mChannel->GetURI(getter_AddRefs(uri));
3203 if (!aRv.Failed()) {
3204 uri->GetScheme(scheme);
3205 if (scheme.LowerCaseEqualsLiteral("jar")) {
3206 mIsMappedArrayBuffer = true;
3211 aRv = InitiateFetch(uploadStream.forget(), mUploadTotal, uploadContentType);
3212 if (aRv.Failed()) {
3213 return;
3216 // Start our timeout
3217 mRequestSentTime = PR_Now();
3218 StartTimeoutTimer();
3220 mWaitingForOnStopRequest = true;
3222 // Step 8
3223 mFlagSend = true;
3225 // If we're synchronous, spin an event loop here and wait
3226 RefPtr<Document> suspendedDoc;
3227 if (mFlagSynchronous) {
3228 auto scopeExit = MakeScopeExit([&] {
3229 CancelSyncTimeoutTimer();
3230 ResumeTimeout();
3231 ResumeEventDispatching();
3233 Maybe<AutoSuppressEventHandling> autoSuppress;
3235 mFlagSyncLooping = true;
3237 if (GetOwner()) {
3238 if (nsCOMPtr<nsPIDOMWindowOuter> topWindow =
3239 GetOwner()->GetOuterWindow()->GetInProcessTop()) {
3240 if (nsCOMPtr<nsPIDOMWindowInner> topInner =
3241 topWindow->GetCurrentInnerWindow()) {
3242 suspendedDoc = topWindow->GetExtantDoc();
3243 autoSuppress.emplace(topWindow->GetBrowsingContext());
3244 topInner->Suspend();
3245 mResumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
3250 SuspendEventDispatching();
3251 StopProgressEventTimer();
3253 SyncTimeoutType syncTimeoutType = MaybeStartSyncTimeoutTimer();
3254 if (syncTimeoutType == eErrorOrExpired) {
3255 Abort();
3256 aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
3257 return;
3260 nsAutoSyncOperation sync(suspendedDoc,
3261 SyncOperationBehavior::eSuspendInput);
3262 if (!SpinEventLoopUntil("XMLHttpRequestMainThread::SendInternal"_ns,
3263 [&]() { return !mFlagSyncLooping; })) {
3264 aRv.Throw(NS_ERROR_UNEXPECTED);
3265 return;
3268 // Time expired... We should throw.
3269 if (syncTimeoutType == eTimerStarted && !mSyncTimeoutTimer) {
3270 aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
3271 return;
3273 } else {
3274 // Now that we've successfully opened the channel, we can change state. Note
3275 // that this needs to come after the AsyncOpen() and rv check, because this
3276 // can run script that would try to restart this request, and that could end
3277 // up doing our AsyncOpen on a null channel if the reentered AsyncOpen
3278 // fails.
3279 StopProgressEventTimer();
3281 // Upload phase beginning; start the progress event timer if necessary.
3282 if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) {
3283 StartProgressEventTimer();
3285 // Dispatch loadstart events
3286 DispatchProgressEvent(this, Events::loadstart, 0, -1);
3287 if (mUpload && !mUploadComplete) {
3288 DispatchProgressEvent(mUpload, Events::loadstart, 0, mUploadTotal);
3292 if (!mChannel) {
3293 aRv = MaybeSilentSendFailure(NS_ERROR_DOM_NETWORK_ERR);
3297 // http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader
3298 void XMLHttpRequestMainThread::SetRequestHeader(const nsACString& aName,
3299 const nsACString& aValue,
3300 ErrorResult& aRv) {
3301 NOT_CALLABLE_IN_SYNC_SEND_RV
3303 // Step 1
3304 if (mState != XMLHttpRequest_Binding::OPENED) {
3305 aRv.ThrowInvalidStateError("XMLHttpRequest state must be OPENED.");
3306 return;
3309 // Step 2
3310 if (mFlagSend) {
3311 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3312 return;
3315 // Step 3
3316 nsAutoCString value;
3317 NS_TrimHTTPWhitespace(aValue, value);
3319 // Step 4
3320 if (!NS_IsValidHTTPToken(aName) || !NS_IsReasonableHTTPHeaderValue(value)) {
3321 aRv.Throw(NS_ERROR_DOM_INVALID_HEADER_NAME);
3322 return;
3325 // Step 5
3326 bool isPrivilegedCaller = IsSystemXHR();
3327 bool isForbiddenHeader =
3328 nsContentUtils::IsForbiddenRequestHeader(aName, aValue);
3329 if (!isPrivilegedCaller && isForbiddenHeader) {
3330 AutoTArray<nsString, 1> params;
3331 CopyUTF8toUTF16(aName, *params.AppendElement());
3332 LogMessage("ForbiddenHeaderWarning", GetOwner(), params);
3333 return;
3336 // Step 6.1
3337 // Skipping for now, as normalizing the case of header names may not be
3338 // web-compatible. See bug 1285036.
3340 // Step 6.2-6.3
3341 // Gecko-specific: invalid headers can be set by privileged
3342 // callers, but will not merge.
3343 if (isPrivilegedCaller && isForbiddenHeader) {
3344 mAuthorRequestHeaders.Set(aName, value);
3345 } else {
3346 mAuthorRequestHeaders.MergeOrSet(aName, value);
3350 void XMLHttpRequestMainThread::SetTimeout(uint32_t aTimeout, ErrorResult& aRv) {
3351 NOT_CALLABLE_IN_SYNC_SEND_RV
3353 if (mFlagSynchronous && mState != XMLHttpRequest_Binding::UNSENT &&
3354 HasOrHasHadOwner()) {
3355 /* Timeout is not supported for synchronous requests with an owning window,
3356 per XHR2 spec. */
3357 LogMessage("TimeoutSyncXHRWarning", GetOwner());
3358 aRv.ThrowInvalidAccessError(
3359 "synchronous XMLHttpRequests do not support timeout and responseType");
3360 return;
3363 mTimeoutMilliseconds = aTimeout;
3364 if (mRequestSentTime) {
3365 StartTimeoutTimer();
3369 nsIEventTarget* XMLHttpRequestMainThread::GetTimerEventTarget() {
3370 if (nsIGlobalObject* global = GetOwnerGlobal()) {
3371 return global->SerialEventTarget();
3373 return nullptr;
3376 nsresult XMLHttpRequestMainThread::DispatchToMainThread(
3377 already_AddRefed<nsIRunnable> aRunnable) {
3378 DEBUG_WORKERREFS;
3379 if (nsIGlobalObject* global = GetOwnerGlobal()) {
3380 return global->Dispatch(std::move(aRunnable));
3382 return NS_DispatchToMainThread(std::move(aRunnable));
3385 void XMLHttpRequestMainThread::StartTimeoutTimer() {
3386 DEBUG_WORKERREFS;
3387 MOZ_ASSERT(
3388 mRequestSentTime,
3389 "StartTimeoutTimer mustn't be called before the request was sent!");
3390 if (mState == XMLHttpRequest_Binding::DONE) {
3391 // do nothing!
3392 return;
3395 CancelTimeoutTimer();
3397 if (!mTimeoutMilliseconds) {
3398 return;
3401 if (!mTimeoutTimer) {
3402 mTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
3404 uint32_t elapsed =
3405 (uint32_t)((PR_Now() - mRequestSentTime) / PR_USEC_PER_MSEC);
3406 mTimeoutTimer->InitWithCallback(
3407 this, mTimeoutMilliseconds > elapsed ? mTimeoutMilliseconds - elapsed : 0,
3408 nsITimer::TYPE_ONE_SHOT);
3411 uint16_t XMLHttpRequestMainThread::ReadyState() const { return mState; }
3413 void XMLHttpRequestMainThread::OverrideMimeType(const nsAString& aMimeType,
3414 ErrorResult& aRv) {
3415 NOT_CALLABLE_IN_SYNC_SEND_RV
3417 if (mState == XMLHttpRequest_Binding::LOADING ||
3418 mState == XMLHttpRequest_Binding::DONE) {
3419 aRv.ThrowInvalidStateError(
3420 "Cannot call 'overrideMimeType()' on XMLHttpRequest after 'send()' "
3421 "(when its state is LOADING or DONE).");
3422 return;
3425 RefPtr<MimeType> parsed = MimeType::Parse(aMimeType);
3426 if (parsed) {
3427 parsed->Serialize(mOverrideMimeType);
3428 } else {
3429 mOverrideMimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
3433 bool XMLHttpRequestMainThread::MozBackgroundRequest() const {
3434 return mFlagBackgroundRequest;
3437 void XMLHttpRequestMainThread::SetMozBackgroundRequestExternal(
3438 bool aMozBackgroundRequest, ErrorResult& aRv) {
3439 if (!IsSystemXHR()) {
3440 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
3441 return;
3444 if (mState != XMLHttpRequest_Binding::UNSENT) {
3445 // Can't change this while we're in the middle of something.
3446 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3447 return;
3450 mFlagBackgroundRequest = aMozBackgroundRequest;
3453 void XMLHttpRequestMainThread::SetMozBackgroundRequest(
3454 bool aMozBackgroundRequest, ErrorResult& aRv) {
3455 // No errors for this webIDL method on main-thread.
3456 SetMozBackgroundRequestExternal(aMozBackgroundRequest, IgnoreErrors());
3459 void XMLHttpRequestMainThread::SetOriginStack(
3460 UniquePtr<SerializedStackHolder> aOriginStack) {
3461 mOriginStack = std::move(aOriginStack);
3464 void XMLHttpRequestMainThread::SetSource(
3465 UniquePtr<ProfileChunkedBuffer> aSource) {
3466 if (!mChannel) {
3467 return;
3469 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
3471 if (httpChannel) {
3472 httpChannel->SetSource(std::move(aSource));
3476 bool XMLHttpRequestMainThread::WithCredentials() const {
3477 return mFlagACwithCredentials;
3480 void XMLHttpRequestMainThread::SetWithCredentials(bool aWithCredentials,
3481 ErrorResult& aRv) {
3482 NOT_CALLABLE_IN_SYNC_SEND_RV
3484 // Return error if we're already processing a request. Note that we can't use
3485 // ReadyState() here, because it can't differentiate between "opened" and
3486 // "sent", so we use mState directly.
3488 if ((mState != XMLHttpRequest_Binding::UNSENT &&
3489 mState != XMLHttpRequest_Binding::OPENED) ||
3490 mFlagSend || mIsAnon) {
3491 aRv.ThrowInvalidStateError("XMLHttpRequest must not be sending.");
3492 return;
3495 mFlagACwithCredentials = aWithCredentials;
3498 nsresult XMLHttpRequestMainThread::ChangeState(uint16_t aState,
3499 bool aBroadcast) {
3500 mState = aState;
3501 nsresult rv = NS_OK;
3503 if (aState != XMLHttpRequest_Binding::HEADERS_RECEIVED &&
3504 aState != XMLHttpRequest_Binding::LOADING) {
3505 StopProgressEventTimer();
3508 if (aBroadcast &&
3509 (!mFlagSynchronous || aState == XMLHttpRequest_Binding::OPENED ||
3510 aState == XMLHttpRequest_Binding::DONE)) {
3511 rv = FireReadystatechangeEvent();
3514 return rv;
3517 /////////////////////////////////////////////////////
3518 // nsIChannelEventSink methods:
3520 NS_IMETHODIMP
3521 XMLHttpRequestMainThread::AsyncOnChannelRedirect(
3522 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
3523 nsIAsyncVerifyRedirectCallback* callback) {
3524 DEBUG_WORKERREFS;
3525 MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
3527 // Prepare to receive callback
3528 mRedirectCallback = callback;
3529 mNewRedirectChannel = aNewChannel;
3531 if (mChannelEventSink) {
3532 nsCOMPtr<nsIAsyncVerifyRedirectCallback> fwd = EnsureXPCOMifier();
3534 nsresult rv = mChannelEventSink->AsyncOnChannelRedirect(
3535 aOldChannel, aNewChannel, aFlags, fwd);
3536 if (NS_FAILED(rv)) {
3537 mRedirectCallback = nullptr;
3538 mNewRedirectChannel = nullptr;
3540 return rv;
3543 // we need to strip Authentication headers for cross-origin requests
3544 // Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
3545 bool stripAuth =
3546 StaticPrefs::network_fetch_redirect_stripAuthHeader() &&
3547 NS_ShouldRemoveAuthHeaderOnRedirect(aOldChannel, aNewChannel, aFlags);
3549 OnRedirectVerifyCallback(NS_OK, stripAuth);
3551 return NS_OK;
3554 nsresult XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result,
3555 bool aStripAuth) {
3556 DEBUG_WORKERREFS;
3557 NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
3558 NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
3560 if (NS_SUCCEEDED(result)) {
3561 bool rewriteToGET = false;
3562 nsCOMPtr<nsIHttpChannel> oldHttpChannel = GetCurrentHttpChannel();
3563 // Fetch 4.4.11
3564 Unused << oldHttpChannel->ShouldStripRequestBodyHeader(mRequestMethod,
3565 &rewriteToGET);
3567 mChannel = mNewRedirectChannel;
3569 nsCOMPtr<nsIHttpChannel> newHttpChannel(do_QueryInterface(mChannel));
3570 if (newHttpChannel) {
3571 // Ensure all original headers are duplicated for the new channel (bug
3572 // #553888)
3573 mAuthorRequestHeaders.ApplyToChannel(newHttpChannel, rewriteToGET,
3574 aStripAuth);
3576 } else {
3577 mErrorLoad = ErrorType::eRedirect;
3578 mErrorLoadDetail = result;
3581 mNewRedirectChannel = nullptr;
3583 mRedirectCallback->OnRedirectVerifyCallback(result);
3584 mRedirectCallback = nullptr;
3586 // It's important that we return success here. If we return the result code
3587 // that we were passed, JavaScript callers who cancel the redirect will wind
3588 // up throwing an exception in the process.
3589 return NS_OK;
3592 /////////////////////////////////////////////////////
3593 // nsIProgressEventSink methods:
3596 NS_IMETHODIMP
3597 XMLHttpRequestMainThread::OnProgress(nsIRequest* aRequest, int64_t aProgress,
3598 int64_t aProgressMax) {
3599 DEBUG_WORKERREFS;
3600 // When uploading, OnProgress reports also headers in aProgress and
3601 // aProgressMax. So, try to remove the headers, if possible.
3602 bool lengthComputable = (aProgressMax != -1);
3603 if (InUploadPhase()) {
3604 int64_t loaded = aProgress;
3605 if (lengthComputable) {
3606 int64_t headerSize = aProgressMax - mUploadTotal;
3607 loaded -= headerSize;
3609 mUploadTransferred = loaded;
3610 mProgressSinceLastProgressEvent = true;
3612 if (!mFlagSynchronous && !mProgressTimerIsActive) {
3613 StartProgressEventTimer();
3615 } else {
3616 mLoadTotal = aProgressMax;
3617 mLoadTransferred = aProgress;
3618 // OnDataAvailable() handles mProgressSinceLastProgressEvent
3619 // for the download phase.
3622 if (mProgressEventSink) {
3623 mProgressEventSink->OnProgress(aRequest, aProgress, aProgressMax);
3626 return NS_OK;
3629 NS_IMETHODIMP
3630 XMLHttpRequestMainThread::OnStatus(nsIRequest* aRequest, nsresult aStatus,
3631 const char16_t* aStatusArg) {
3632 DEBUG_WORKERREFS;
3633 if (mProgressEventSink) {
3634 mProgressEventSink->OnStatus(aRequest, aStatus, aStatusArg);
3637 return NS_OK;
3640 bool XMLHttpRequestMainThread::AllowUploadProgress() {
3641 return !IsCrossSiteCORSRequest() || mFlagHadUploadListenersOnSend;
3644 /////////////////////////////////////////////////////
3645 // nsIInterfaceRequestor methods:
3647 NS_IMETHODIMP
3648 XMLHttpRequestMainThread::GetInterface(const nsIID& aIID, void** aResult) {
3649 nsresult rv;
3651 // Make sure to return ourselves for the channel event sink interface and
3652 // progress event sink interface, no matter what. We can forward these to
3653 // mNotificationCallbacks if it wants to get notifications for them. But we
3654 // need to see these notifications for proper functioning.
3655 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
3656 mChannelEventSink = do_GetInterface(mNotificationCallbacks);
3657 *aResult = static_cast<nsIChannelEventSink*>(EnsureXPCOMifier().take());
3658 return NS_OK;
3659 } else if (aIID.Equals(NS_GET_IID(nsIProgressEventSink))) {
3660 mProgressEventSink = do_GetInterface(mNotificationCallbacks);
3661 *aResult = static_cast<nsIProgressEventSink*>(EnsureXPCOMifier().take());
3662 return NS_OK;
3665 // Now give mNotificationCallbacks (if non-null) a chance to return the
3666 // desired interface.
3667 if (mNotificationCallbacks) {
3668 rv = mNotificationCallbacks->GetInterface(aIID, aResult);
3669 if (NS_SUCCEEDED(rv)) {
3670 NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!");
3671 return rv;
3675 if (!mFlagBackgroundRequest && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
3676 aIID.Equals(NS_GET_IID(nsIAuthPrompt2)))) {
3677 nsCOMPtr<nsIPromptFactory> wwatch =
3678 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
3679 NS_ENSURE_SUCCESS(rv, rv);
3681 // Get the an auth prompter for our window so that the parenting
3682 // of the dialogs works as it should when using tabs.
3683 nsCOMPtr<nsPIDOMWindowOuter> window;
3684 if (GetOwner()) {
3685 window = GetOwner()->GetOuterWindow();
3687 return wwatch->GetPrompt(window, aIID, reinterpret_cast<void**>(aResult));
3690 // Now check for the various XHR non-DOM interfaces, except
3691 // nsIProgressEventSink and nsIChannelEventSink which we already
3692 // handled above.
3693 if (aIID.Equals(NS_GET_IID(nsIStreamListener))) {
3694 *aResult = static_cast<nsIStreamListener*>(EnsureXPCOMifier().take());
3695 return NS_OK;
3697 if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) {
3698 *aResult = static_cast<nsIRequestObserver*>(EnsureXPCOMifier().take());
3699 return NS_OK;
3701 if (aIID.Equals(NS_GET_IID(nsITimerCallback))) {
3702 *aResult = static_cast<nsITimerCallback*>(EnsureXPCOMifier().take());
3703 return NS_OK;
3706 return QueryInterface(aIID, aResult);
3709 void XMLHttpRequestMainThread::GetInterface(
3710 JSContext* aCx, JS::Handle<JS::Value> aIID,
3711 JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
3712 dom::GetInterface(aCx, this, aIID, aRetval, aRv);
3715 XMLHttpRequestUpload* XMLHttpRequestMainThread::GetUpload(ErrorResult& aRv) {
3716 if (!mUpload) {
3717 mUpload = new XMLHttpRequestUpload(this);
3719 return mUpload;
3722 bool XMLHttpRequestMainThread::MozAnon() const { return mIsAnon; }
3724 bool XMLHttpRequestMainThread::MozSystem() const { return IsSystemXHR(); }
3726 void XMLHttpRequestMainThread::HandleTimeoutCallback() {
3727 DEBUG_WORKERREFS;
3728 if (mState == XMLHttpRequest_Binding::DONE) {
3729 MOZ_ASSERT_UNREACHABLE(
3730 "XMLHttpRequestMainThread::HandleTimeoutCallback "
3731 "with completed request");
3732 // do nothing!
3733 return;
3736 mFlagTimedOut = true;
3737 CloseRequestWithError(Events::timeout);
3740 void XMLHttpRequestMainThread::CancelTimeoutTimer() {
3741 DEBUG_WORKERREFS;
3742 if (mTimeoutTimer) {
3743 mTimeoutTimer->Cancel();
3744 mTimeoutTimer = nullptr;
3748 NS_IMETHODIMP
3749 XMLHttpRequestMainThread::Notify(nsITimer* aTimer) {
3750 DEBUG_WORKERREFS;
3751 if (mProgressNotifier == aTimer) {
3752 HandleProgressTimerCallback();
3753 return NS_OK;
3756 if (mTimeoutTimer == aTimer) {
3757 HandleTimeoutCallback();
3758 return NS_OK;
3761 if (mSyncTimeoutTimer == aTimer) {
3762 HandleSyncTimeoutTimer();
3763 return NS_OK;
3766 // Just in case some JS user wants to QI to nsITimerCallback and play with
3767 // us...
3768 NS_WARNING("Unexpected timer!");
3769 return NS_ERROR_INVALID_POINTER;
3772 void XMLHttpRequestMainThread::HandleProgressTimerCallback() {
3773 DEBUG_WORKERREFS;
3774 // Don't fire the progress event if mLoadTotal is 0, see XHR spec step 6.1
3775 if (!mLoadTotal && mLoadTransferred) {
3776 return;
3779 mProgressTimerIsActive = false;
3781 if (!mProgressSinceLastProgressEvent || mErrorLoad != ErrorType::eOK) {
3782 return;
3785 if (InUploadPhase()) {
3786 if (mUpload && !mUploadComplete && mFlagHadUploadListenersOnSend) {
3787 DispatchProgressEvent(mUpload, Events::progress, mUploadTransferred,
3788 mUploadTotal);
3790 } else {
3791 FireReadystatechangeEvent();
3792 DispatchProgressEvent(this, Events::progress, mLoadTransferred, mLoadTotal);
3795 mProgressSinceLastProgressEvent = false;
3797 StartProgressEventTimer();
3800 void XMLHttpRequestMainThread::StopProgressEventTimer() {
3801 if (mProgressNotifier) {
3802 mProgressTimerIsActive = false;
3803 mProgressNotifier->Cancel();
3807 void XMLHttpRequestMainThread::StartProgressEventTimer() {
3808 if (!mProgressNotifier) {
3809 mProgressNotifier = NS_NewTimer(GetTimerEventTarget());
3811 if (mProgressNotifier) {
3812 mProgressTimerIsActive = true;
3813 mProgressNotifier->Cancel();
3814 mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
3815 nsITimer::TYPE_ONE_SHOT);
3819 XMLHttpRequestMainThread::SyncTimeoutType
3820 XMLHttpRequestMainThread::MaybeStartSyncTimeoutTimer() {
3821 MOZ_ASSERT(mFlagSynchronous);
3823 Document* doc = GetDocumentIfCurrent();
3824 if (!doc || !doc->GetPageUnloadingEventTimeStamp()) {
3825 return eNoTimerNeeded;
3828 // If we are in a beforeunload or a unload event, we must force a timeout.
3829 TimeDuration diff =
3830 (TimeStamp::NowLoRes() - doc->GetPageUnloadingEventTimeStamp());
3831 if (diff.ToMilliseconds() > MAX_SYNC_TIMEOUT_WHEN_UNLOADING) {
3832 return eErrorOrExpired;
3835 mSyncTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
3836 if (!mSyncTimeoutTimer) {
3837 return eErrorOrExpired;
3840 uint32_t timeout = MAX_SYNC_TIMEOUT_WHEN_UNLOADING - diff.ToMilliseconds();
3841 nsresult rv = mSyncTimeoutTimer->InitWithCallback(this, timeout,
3842 nsITimer::TYPE_ONE_SHOT);
3843 return NS_FAILED(rv) ? eErrorOrExpired : eTimerStarted;
3846 void XMLHttpRequestMainThread::HandleSyncTimeoutTimer() {
3847 MOZ_ASSERT(mSyncTimeoutTimer);
3848 MOZ_ASSERT(mFlagSyncLooping);
3850 CancelSyncTimeoutTimer();
3851 Abort();
3852 mErrorLoadDetail = NS_ERROR_DOM_TIMEOUT_ERR;
3855 void XMLHttpRequestMainThread::CancelSyncTimeoutTimer() {
3856 if (mSyncTimeoutTimer) {
3857 mSyncTimeoutTimer->Cancel();
3858 mSyncTimeoutTimer = nullptr;
3862 already_AddRefed<nsXMLHttpRequestXPCOMifier>
3863 XMLHttpRequestMainThread::EnsureXPCOMifier() {
3864 if (!mXPCOMifier) {
3865 mXPCOMifier = new nsXMLHttpRequestXPCOMifier(this);
3867 RefPtr<nsXMLHttpRequestXPCOMifier> newRef(mXPCOMifier);
3868 return newRef.forget();
3871 bool XMLHttpRequestMainThread::ShouldBlockAuthPrompt() {
3872 // Verify that it's ok to prompt for credentials here, per spec
3873 // http://xhr.spec.whatwg.org/#the-send%28%29-method
3875 if (mAuthorRequestHeaders.Has("authorization")) {
3876 return true;
3879 nsCOMPtr<nsIURI> uri;
3880 nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
3881 if (NS_WARN_IF(NS_FAILED(rv))) {
3882 return false;
3885 // Also skip if a username and/or password is provided in the URI.
3886 bool hasUserPass;
3887 return NS_SUCCEEDED(uri->GetHasUserPass(&hasUserPass)) && hasUserPass;
3890 void XMLHttpRequestMainThread::TruncateResponseText() {
3891 mResponseText.Truncate();
3892 XMLHttpRequest_Binding::ClearCachedResponseTextValue(this);
3895 NS_IMPL_ISUPPORTS(XMLHttpRequestMainThread::nsHeaderVisitor,
3896 nsIHttpHeaderVisitor)
3898 NS_IMETHODIMP XMLHttpRequestMainThread::nsHeaderVisitor::VisitHeader(
3899 const nsACString& header, const nsACString& value) {
3900 if (mXHR.IsSafeHeader(header, mHttpChannel)) {
3901 nsAutoCString lowerHeader(header);
3902 ToLowerCase(lowerHeader);
3903 if (!mHeaderList.InsertElementSorted(HeaderEntry(lowerHeader, value),
3904 fallible)) {
3905 return NS_ERROR_OUT_OF_MEMORY;
3908 return NS_OK;
3911 XMLHttpRequestMainThread::nsHeaderVisitor::nsHeaderVisitor(
3912 const XMLHttpRequestMainThread& aXMLHttpRequest,
3913 NotNull<nsIHttpChannel*> aHttpChannel)
3914 : mXHR(aXMLHttpRequest), mHttpChannel(aHttpChannel) {}
3916 XMLHttpRequestMainThread::nsHeaderVisitor::~nsHeaderVisitor() = default;
3918 void XMLHttpRequestMainThread::MaybeCreateBlobStorage() {
3919 DEBUG_WORKERREFS;
3920 MOZ_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
3922 if (mBlobStorage) {
3923 return;
3926 MutableBlobStorage::MutableBlobStorageType storageType =
3927 BasePrincipal::Cast(mPrincipal)->PrivateBrowsingId() == 0
3928 ? MutableBlobStorage::eCouldBeInTemporaryFile
3929 : MutableBlobStorage::eOnlyInMemory;
3931 nsCOMPtr<nsIEventTarget> eventTarget;
3932 if (nsIGlobalObject* global = GetOwnerGlobal()) {
3933 eventTarget = global->SerialEventTarget();
3936 mBlobStorage = new MutableBlobStorage(storageType, eventTarget);
3939 void XMLHttpRequestMainThread::BlobStoreCompleted(
3940 MutableBlobStorage* aBlobStorage, BlobImpl* aBlobImpl, nsresult aRv) {
3941 DEBUG_WORKERREFS;
3942 // Ok, the state is changed...
3943 if (mBlobStorage != aBlobStorage || NS_FAILED(aRv)) {
3944 return;
3947 MOZ_ASSERT(mState != XMLHttpRequest_Binding::DONE);
3949 mResponseBlobImpl = aBlobImpl;
3950 mBlobStorage = nullptr;
3952 ChangeStateToDone(mFlagSyncLooping);
3955 NS_IMETHODIMP
3956 XMLHttpRequestMainThread::GetName(nsACString& aName) {
3957 aName.AssignLiteral("XMLHttpRequest");
3958 return NS_OK;
3961 // nsXMLHttpRequestXPCOMifier implementation
3962 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpRequestXPCOMifier)
3963 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
3964 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
3965 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
3966 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
3967 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
3968 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
3969 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
3970 NS_INTERFACE_MAP_ENTRY(nsINamed)
3971 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
3972 NS_INTERFACE_MAP_END
3974 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpRequestXPCOMifier)
3975 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpRequestXPCOMifier)
3977 // Can't NS_IMPL_CYCLE_COLLECTION( because mXHR has ambiguous
3978 // inheritance from nsISupports.
3979 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequestXPCOMifier)
3981 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpRequestXPCOMifier)
3982 if (tmp->mXHR) {
3983 tmp->mXHR->mXPCOMifier = nullptr;
3985 NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR)
3986 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
3988 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpRequestXPCOMifier)
3989 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXHR)
3990 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
3992 NS_IMETHODIMP
3993 nsXMLHttpRequestXPCOMifier::GetInterface(const nsIID& aIID, void** aResult) {
3994 // Return ourselves for the things we implement (except
3995 // nsIInterfaceRequestor) and the XHR for the rest.
3996 if (!aIID.Equals(NS_GET_IID(nsIInterfaceRequestor))) {
3997 nsresult rv = QueryInterface(aIID, aResult);
3998 if (NS_SUCCEEDED(rv)) {
3999 return rv;
4003 return mXHR->GetInterface(aIID, aResult);
4006 ArrayBufferBuilder::ArrayBufferBuilder()
4007 : mMutex("ArrayBufferBuilder"),
4008 mDataPtr(nullptr),
4009 mCapacity(0),
4010 mLength(0),
4011 mMapPtr(nullptr),
4012 mNeutered(false) {}
4014 ArrayBufferBuilder::~ArrayBufferBuilder() {
4015 if (mDataPtr) {
4016 JS_free(nullptr, mDataPtr);
4019 if (mMapPtr) {
4020 JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
4021 mMapPtr = nullptr;
4024 mDataPtr = nullptr;
4025 mCapacity = mLength = 0;
4028 bool ArrayBufferBuilder::SetCapacity(uint32_t aNewCap) {
4029 MutexAutoLock lock(mMutex);
4030 return SetCapacityInternal(aNewCap, lock);
4033 bool ArrayBufferBuilder::SetCapacityInternal(
4034 uint32_t aNewCap, const MutexAutoLock& aProofOfLock) {
4035 MOZ_ASSERT(!mMapPtr);
4036 MOZ_ASSERT(!mNeutered);
4038 // To ensure that realloc won't free mDataPtr, use a size of 1
4039 // instead of 0.
4040 uint8_t* newdata = (uint8_t*)js_realloc(mDataPtr, aNewCap ? aNewCap : 1);
4042 if (!newdata) {
4043 return false;
4046 if (aNewCap > mCapacity) {
4047 memset(newdata + mCapacity, 0, aNewCap - mCapacity);
4050 mDataPtr = newdata;
4051 mCapacity = aNewCap;
4052 if (mLength > aNewCap) {
4053 mLength = aNewCap;
4056 return true;
4059 bool ArrayBufferBuilder::Append(const uint8_t* aNewData, uint32_t aDataLen,
4060 uint32_t aMaxGrowth) {
4061 MutexAutoLock lock(mMutex);
4062 MOZ_ASSERT(!mMapPtr);
4063 MOZ_ASSERT(!mNeutered);
4065 CheckedUint32 neededCapacity = mLength;
4066 neededCapacity += aDataLen;
4067 if (!neededCapacity.isValid()) {
4068 return false;
4070 if (mLength + aDataLen > mCapacity) {
4071 CheckedUint32 newcap = mCapacity;
4072 // Double while under aMaxGrowth or if not specified.
4073 if (!aMaxGrowth || mCapacity < aMaxGrowth) {
4074 newcap *= 2;
4075 } else {
4076 newcap += aMaxGrowth;
4079 if (!newcap.isValid()) {
4080 return false;
4083 // But make sure there's always enough to satisfy our request.
4084 if (newcap.value() < neededCapacity.value()) {
4085 newcap = neededCapacity;
4088 if (!SetCapacityInternal(newcap.value(), lock)) {
4089 return false;
4093 // Assert that the region isn't overlapping so we can memcpy.
4094 MOZ_ASSERT(
4095 !AreOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength, aDataLen));
4097 memcpy(mDataPtr + mLength, aNewData, aDataLen);
4098 mLength += aDataLen;
4100 return true;
4103 uint32_t ArrayBufferBuilder::Length() {
4104 MutexAutoLock lock(mMutex);
4105 MOZ_ASSERT(!mNeutered);
4106 return mLength;
4109 uint32_t ArrayBufferBuilder::Capacity() {
4110 MutexAutoLock lock(mMutex);
4111 MOZ_ASSERT(!mNeutered);
4112 return mCapacity;
4115 JSObject* ArrayBufferBuilder::TakeArrayBuffer(JSContext* aCx) {
4116 MutexAutoLock lock(mMutex);
4117 MOZ_DIAGNOSTIC_ASSERT(!mNeutered);
4119 if (mMapPtr) {
4120 JSObject* obj = JS::NewMappedArrayBufferWithContents(aCx, mLength, mMapPtr);
4121 if (!obj) {
4122 JS::ReleaseMappedArrayBufferContents(mMapPtr, mLength);
4125 mMapPtr = nullptr;
4126 mNeutered = true;
4128 // The memory-mapped contents will be released when the ArrayBuffer becomes
4129 // detached or is GC'd.
4130 return obj;
4133 // we need to check for mLength == 0, because nothing may have been
4134 // added
4135 if (mCapacity > mLength || mLength == 0) {
4136 if (!SetCapacityInternal(mLength, lock)) {
4137 return nullptr;
4141 // |mDataPtr| will be deallocated in ArrayBufferBuilder's destructor when this
4142 // ArrayBuffer allocation failed.
4143 JSObject* obj = JS::NewArrayBufferWithContents(
4144 aCx, mLength, mDataPtr,
4145 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory);
4146 if (!obj) {
4147 return nullptr;
4150 mDataPtr = nullptr;
4151 mCapacity = mLength = 0;
4153 mNeutered = true;
4154 return obj;
4157 nsresult ArrayBufferBuilder::MapToFileInPackage(const nsCString& aFile,
4158 nsIFile* aJarFile) {
4159 MutexAutoLock lock(mMutex);
4160 MOZ_ASSERT(NS_IsMainThread());
4161 MOZ_ASSERT(!mNeutered);
4163 nsresult rv;
4165 // Open Jar file to get related attributes of target file.
4166 RefPtr<nsZipArchive> zip = nsZipArchive::OpenArchive(aJarFile);
4167 if (!zip) {
4168 return NS_ERROR_FAILURE;
4170 nsZipItem* zipItem = zip->GetItem(aFile.get());
4171 if (!zipItem) {
4172 return NS_ERROR_FILE_NOT_FOUND;
4175 // If file was added to the package as stored(uncompressed), map to the
4176 // offset of file in zip package.
4177 if (!zipItem->Compression()) {
4178 uint32_t offset = zip->GetDataOffset(zipItem);
4179 uint32_t size = zipItem->RealSize();
4180 mozilla::AutoFDClose pr_fd;
4181 rv = aJarFile->OpenNSPRFileDesc(PR_RDONLY, 0, getter_Transfers(pr_fd));
4182 if (NS_FAILED(rv)) {
4183 return rv;
4185 mMapPtr = JS::CreateMappedArrayBufferContents(
4186 PR_FileDesc2NativeHandle(pr_fd.get()), offset, size);
4187 if (mMapPtr) {
4188 mLength = size;
4189 return NS_OK;
4192 return NS_ERROR_FAILURE;
4195 /* static */
4196 bool ArrayBufferBuilder::AreOverlappingRegions(const uint8_t* aStart1,
4197 uint32_t aLength1,
4198 const uint8_t* aStart2,
4199 uint32_t aLength2) {
4200 const uint8_t* end1 = aStart1 + aLength1;
4201 const uint8_t* end2 = aStart2 + aLength2;
4203 const uint8_t* max_start = aStart1 > aStart2 ? aStart1 : aStart2;
4204 const uint8_t* min_end = end1 < end2 ? end1 : end2;
4206 return max_start < min_end;
4209 RequestHeaders::RequestHeader* RequestHeaders::Find(const nsACString& aName) {
4210 for (RequestHeaders::RequestHeader& header : mHeaders) {
4211 if (header.mName.Equals(aName, nsCaseInsensitiveCStringComparator)) {
4212 return &header;
4215 return nullptr;
4218 bool RequestHeaders::IsEmpty() const { return mHeaders.IsEmpty(); }
4220 bool RequestHeaders::Has(const char* aName) {
4221 return Has(nsDependentCString(aName));
4224 bool RequestHeaders::Has(const nsACString& aName) { return !!Find(aName); }
4226 void RequestHeaders::Get(const char* aName, nsACString& aValue) {
4227 Get(nsDependentCString(aName), aValue);
4230 void RequestHeaders::Get(const nsACString& aName, nsACString& aValue) {
4231 RequestHeader* header = Find(aName);
4232 if (header) {
4233 aValue = header->mValue;
4234 } else {
4235 aValue.SetIsVoid(true);
4239 void RequestHeaders::Set(const char* aName, const nsACString& aValue) {
4240 Set(nsDependentCString(aName), aValue);
4243 void RequestHeaders::Set(const nsACString& aName, const nsACString& aValue) {
4244 RequestHeader* header = Find(aName);
4245 if (header) {
4246 header->mValue.Assign(aValue);
4247 } else {
4248 RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
4249 mHeaders.AppendElement(newHeader);
4253 void RequestHeaders::MergeOrSet(const char* aName, const nsACString& aValue) {
4254 MergeOrSet(nsDependentCString(aName), aValue);
4257 void RequestHeaders::MergeOrSet(const nsACString& aName,
4258 const nsACString& aValue) {
4259 RequestHeader* header = Find(aName);
4260 if (header) {
4261 header->mValue.AppendLiteral(", ");
4262 header->mValue.Append(aValue);
4263 } else {
4264 RequestHeader newHeader = {nsCString(aName), nsCString(aValue)};
4265 mHeaders.AppendElement(newHeader);
4269 void RequestHeaders::Clear() { mHeaders.Clear(); }
4271 void RequestHeaders::ApplyToChannel(nsIHttpChannel* aChannel,
4272 bool aStripRequestBodyHeader,
4273 bool aStripAuthHeader) const {
4274 for (const RequestHeader& header : mHeaders) {
4275 if (aStripRequestBodyHeader &&
4276 (header.mName.LowerCaseEqualsASCII("content-type") ||
4277 header.mName.LowerCaseEqualsASCII("content-encoding") ||
4278 header.mName.LowerCaseEqualsASCII("content-language") ||
4279 header.mName.LowerCaseEqualsASCII("content-location"))) {
4280 continue;
4283 if (aStripAuthHeader &&
4284 header.mName.LowerCaseEqualsASCII("authorization")) {
4285 continue;
4288 // Update referrerInfo to override referrer header in system privileged.
4289 if (header.mName.LowerCaseEqualsASCII("referer")) {
4290 DebugOnly<nsresult> rv = aChannel->SetNewReferrerInfo(
4291 header.mValue, nsIReferrerInfo::ReferrerPolicyIDL::UNSAFE_URL, true);
4292 MOZ_ASSERT(NS_SUCCEEDED(rv));
4294 if (header.mValue.IsEmpty()) {
4295 DebugOnly<nsresult> rv = aChannel->SetEmptyRequestHeader(header.mName);
4296 MOZ_ASSERT(NS_SUCCEEDED(rv));
4297 } else {
4298 DebugOnly<nsresult> rv =
4299 aChannel->SetRequestHeader(header.mName, header.mValue, false);
4300 MOZ_ASSERT(NS_SUCCEEDED(rv));
4305 void RequestHeaders::GetCORSUnsafeHeaders(nsTArray<nsCString>& aArray) const {
4306 for (const RequestHeader& header : mHeaders) {
4307 if (!nsContentUtils::IsCORSSafelistedRequestHeader(header.mName,
4308 header.mValue)) {
4309 aArray.AppendElement(header.mName);
4314 RequestHeaders::CharsetIterator::CharsetIterator(nsACString& aSource)
4315 : mValid(false),
4316 mCurPos(-1),
4317 mCurLen(-1),
4318 mCutoff(aSource.Length()),
4319 mSource(aSource) {}
4321 bool RequestHeaders::CharsetIterator::Equals(
4322 const nsACString& aOther, const nsCStringComparator& aCmp) const {
4323 if (mValid) {
4324 return Substring(mSource, mCurPos, mCurLen).Equals(aOther, aCmp);
4325 } else {
4326 return false;
4330 void RequestHeaders::CharsetIterator::Replace(const nsACString& aReplacement) {
4331 if (mValid) {
4332 mSource.Replace(mCurPos, mCurLen, aReplacement);
4333 mCurLen = aReplacement.Length();
4337 bool RequestHeaders::CharsetIterator::Next() {
4338 int32_t start, end;
4339 nsAutoCString charset;
4341 // Look for another charset declaration in the string, limiting the
4342 // search to only the characters before the parts we've already searched
4343 // (before mCutoff), so that we don't find the same charset twice.
4344 NS_ExtractCharsetFromContentType(Substring(mSource, 0, mCutoff), charset,
4345 &mValid, &start, &end);
4347 if (!mValid) {
4348 return false;
4351 // Everything after the = sign is the part of the charset we want.
4352 mCurPos = mSource.FindChar('=', start) + 1;
4353 mCurLen = end - mCurPos;
4355 // Special case: the extracted charset is quoted with single quotes.
4356 // For the purpose of preserving what was set we want to handle them
4357 // as delimiters (although they aren't really).
4358 if (charset.Length() >= 2 && charset.First() == '\'' &&
4359 charset.Last() == '\'') {
4360 ++mCurPos;
4361 mCurLen -= 2;
4364 mCutoff = start;
4366 return true;
4369 } // namespace mozilla::dom