summaryrefslogtreecommitdiffstats
path: root/dom/xhr
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/xhr
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/xhr')
-rw-r--r--dom/xhr/XMLHttpRequest.cpp38
-rw-r--r--dom/xhr/XMLHttpRequest.h187
-rw-r--r--dom/xhr/XMLHttpRequestEventTarget.cpp36
-rw-r--r--dom/xhr/XMLHttpRequestEventTarget.h55
-rw-r--r--dom/xhr/XMLHttpRequestMainThread.cpp4168
-rw-r--r--dom/xhr/XMLHttpRequestMainThread.h872
-rw-r--r--dom/xhr/XMLHttpRequestString.cpp267
-rw-r--r--dom/xhr/XMLHttpRequestString.h161
-rw-r--r--dom/xhr/XMLHttpRequestUpload.cpp27
-rw-r--r--dom/xhr/XMLHttpRequestUpload.h50
-rw-r--r--dom/xhr/XMLHttpRequestWorker.cpp2455
-rw-r--r--dom/xhr/XMLHttpRequestWorker.h334
-rw-r--r--dom/xhr/moz.build40
-rw-r--r--dom/xhr/nsIXMLHttpRequest.idl349
-rw-r--r--dom/xhr/tests/browser.ini5
-rw-r--r--dom/xhr/tests/browser_xhr_onchange_leak.html25
-rw-r--r--dom/xhr/tests/browser_xhr_onchange_leak.js23
-rw-r--r--dom/xhr/tests/common_temporaryFileBlob.js100
-rw-r--r--dom/xhr/tests/echo.sjs21
-rw-r--r--dom/xhr/tests/empty.html0
-rw-r--r--dom/xhr/tests/file_XHRDocURI.html9
-rw-r--r--dom/xhr/tests/file_XHRDocURI.html^headers^3
-rw-r--r--dom/xhr/tests/file_XHRDocURI.sjs13
-rw-r--r--dom/xhr/tests/file_XHRDocURI.text1
-rw-r--r--dom/xhr/tests/file_XHRDocURI.text^headers^3
-rw-r--r--dom/xhr/tests/file_XHRDocURI.xml1
-rw-r--r--dom/xhr/tests/file_XHRDocURI.xml^headers^3
-rw-r--r--dom/xhr/tests/file_XHRResponseURL.js303
-rw-r--r--dom/xhr/tests/file_XHRResponseURL.sjs14
-rw-r--r--dom/xhr/tests/file_XHRResponseURL.text1
-rw-r--r--dom/xhr/tests/file_XHRResponseURL.text^headers^3
-rw-r--r--dom/xhr/tests/file_XHRResponseURL_nocors.text1
-rw-r--r--dom/xhr/tests/file_XHRSendData.sjs30
-rw-r--r--dom/xhr/tests/file_XHRSendData_doc.xml2
-rw-r--r--dom/xhr/tests/file_XHRSendData_doc.xml^headers^1
-rw-r--r--dom/xhr/tests/file_XHR_anon.sjs24
-rw-r--r--dom/xhr/tests/file_XHR_binary1.binbin0 -> 12 bytes
-rw-r--r--dom/xhr/tests/file_XHR_binary1.bin^headers^1
-rw-r--r--dom/xhr/tests/file_XHR_binary2.binbin0 -> 65536 bytes
-rw-r--r--dom/xhr/tests/file_XHR_fail1.txt1
-rw-r--r--dom/xhr/tests/file_XHR_fail1.txt^headers^2
-rw-r--r--dom/xhr/tests/file_XHR_fail1b.txt1
-rw-r--r--dom/xhr/tests/file_XHR_header.sjs6
-rw-r--r--dom/xhr/tests/file_XHR_pass1.xml1
-rw-r--r--dom/xhr/tests/file_XHR_pass2.txt1
-rw-r--r--dom/xhr/tests/file_XHR_pass3.txt1
-rw-r--r--dom/xhr/tests/file_XHR_pass3.txt^headers^2
-rw-r--r--dom/xhr/tests/file_XHR_system_redirect.html5
-rw-r--r--dom/xhr/tests/file_XHR_system_redirect.html^headers^2
-rw-r--r--dom/xhr/tests/file_XHR_timeout.sjs15
-rw-r--r--dom/xhr/tests/file_html_in_xhr.html16
-rw-r--r--dom/xhr/tests/file_html_in_xhr.sjs15
-rw-r--r--dom/xhr/tests/file_html_in_xhr2.html1
-rw-r--r--dom/xhr/tests/file_html_in_xhr3.html1
-rw-r--r--dom/xhr/tests/file_sync_xhr_document_write_with_iframe.html21
-rw-r--r--dom/xhr/tests/iframe_sync_xhr_unload.html18
-rw-r--r--dom/xhr/tests/mochitest.ini115
-rw-r--r--dom/xhr/tests/progressserver.sjs52
-rw-r--r--dom/xhr/tests/relativeLoad_import.js5
-rw-r--r--dom/xhr/tests/relativeLoad_worker.js27
-rw-r--r--dom/xhr/tests/relativeLoad_worker2.js9
-rw-r--r--dom/xhr/tests/responseIdentical.sjs17
-rw-r--r--dom/xhr/tests/subdir/relativeLoad_sub_import.js5
-rw-r--r--dom/xhr/tests/subdir/relativeLoad_sub_worker.js26
-rw-r--r--dom/xhr/tests/subdir/relativeLoad_sub_worker2.js9
-rw-r--r--dom/xhr/tests/sync_xhr_unload.sjs15
-rw-r--r--dom/xhr/tests/temporaryFileBlob.sjs33
-rw-r--r--dom/xhr/tests/terminateSyncXHR_worker.js19
-rw-r--r--dom/xhr/tests/test_XHR.html393
-rw-r--r--dom/xhr/tests/test_XHRDocURI.html513
-rw-r--r--dom/xhr/tests/test_XHRResponseURL.html69
-rw-r--r--dom/xhr/tests/test_XHRSendData.html267
-rw-r--r--dom/xhr/tests/test_XHR_anon.html180
-rw-r--r--dom/xhr/tests/test_XHR_header.html31
-rw-r--r--dom/xhr/tests/test_XHR_onuploadprogress.html40
-rw-r--r--dom/xhr/tests/test_XHR_parameters.html97
-rw-r--r--dom/xhr/tests/test_XHR_system.html99
-rw-r--r--dom/xhr/tests/test_XHR_timeout.html60
-rw-r--r--dom/xhr/tests/test_XHR_timeout.js342
-rw-r--r--dom/xhr/tests/test_bug1300552.html29
-rw-r--r--dom/xhr/tests/test_html_in_xhr.html113
-rw-r--r--dom/xhr/tests/test_relativeLoad.html52
-rw-r--r--dom/xhr/tests/test_sync_xhr_document_write_with_iframe.html28
-rw-r--r--dom/xhr/tests/test_sync_xhr_timer.xhtml52
-rw-r--r--dom/xhr/tests/test_sync_xhr_unload.html36
-rw-r--r--dom/xhr/tests/test_temporaryFileBlob.html40
-rw-r--r--dom/xhr/tests/test_worker_terminateSyncXHR.html45
-rw-r--r--dom/xhr/tests/test_worker_xhr.html73
-rw-r--r--dom/xhr/tests/test_worker_xhr2.html38
-rw-r--r--dom/xhr/tests/test_worker_xhrAbort.html44
-rw-r--r--dom/xhr/tests/test_worker_xhr_3rdparty.html74
-rw-r--r--dom/xhr/tests/test_worker_xhr_cors_redirect.html35
-rw-r--r--dom/xhr/tests/test_worker_xhr_headers.html86
-rw-r--r--dom/xhr/tests/test_worker_xhr_implicit_cancel.html44
-rw-r--r--dom/xhr/tests/test_worker_xhr_parameters.html64
-rw-r--r--dom/xhr/tests/test_worker_xhr_parameters.js75
-rw-r--r--dom/xhr/tests/test_worker_xhr_responseURL.html76
-rw-r--r--dom/xhr/tests/test_worker_xhr_system.html52
-rw-r--r--dom/xhr/tests/test_worker_xhr_system.js30
-rw-r--r--dom/xhr/tests/test_worker_xhr_timeout.html58
-rw-r--r--dom/xhr/tests/test_xhr_abort_after_load.html96
-rw-r--r--dom/xhr/tests/test_xhr_forbidden_headers.html96
-rw-r--r--dom/xhr/tests/test_xhr_overridemimetype_throws_on_invalid_state.html61
-rw-r--r--dom/xhr/tests/test_xhr_progressevents.html333
-rw-r--r--dom/xhr/tests/test_xhr_send.html83
-rw-r--r--dom/xhr/tests/test_xhr_send_readystate.html39
-rw-r--r--dom/xhr/tests/test_xhr_withCredentials.html35
-rw-r--r--dom/xhr/tests/worker_bug1300552.js33
-rw-r--r--dom/xhr/tests/worker_file_getcookie.sjs15
-rw-r--r--dom/xhr/tests/worker_temporaryFileBlob.js29
-rw-r--r--dom/xhr/tests/worker_terminateSyncXHR_frame.html25
-rw-r--r--dom/xhr/tests/worker_testXHR.txt1
-rw-r--r--dom/xhr/tests/worker_xhr_cors_redirect.js10
-rw-r--r--dom/xhr/tests/worker_xhr_cors_redirect.sjs10
-rw-r--r--dom/xhr/tests/worker_xhr_headers_server.sjs66
-rw-r--r--dom/xhr/tests/worker_xhr_headers_worker.js16
-rw-r--r--dom/xhr/tests/xhr2_worker.js163
-rw-r--r--dom/xhr/tests/xhrAbort_worker.js92
-rw-r--r--dom/xhr/tests/xhr_implicit_cancel_worker.js10
-rw-r--r--dom/xhr/tests/xhr_worker.js84
120 files changed, 14599 insertions, 0 deletions
diff --git a/dom/xhr/XMLHttpRequest.cpp b/dom/xhr/XMLHttpRequest.cpp
new file mode 100644
index 000000000..36abb3835
--- /dev/null
+++ b/dom/xhr/XMLHttpRequest.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "XMLHttpRequest.h"
+#include "XMLHttpRequestMainThread.h"
+#include "XMLHttpRequestWorker.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */ already_AddRefed<XMLHttpRequest>
+XMLHttpRequest::Constructor(const GlobalObject& aGlobal,
+ const MozXMLHttpRequestParameters& aParams,
+ ErrorResult& aRv)
+{
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ nsCOMPtr<nsIScriptObjectPrincipal> principal =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global || ! principal) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<XMLHttpRequestMainThread> req = new XMLHttpRequestMainThread();
+ req->Construct(principal->GetPrincipal(), global);
+ req->InitParameters(aParams.mMozAnon, aParams.mMozSystem);
+ return req.forget();
+ }
+
+ return XMLHttpRequestWorker::Construct(aGlobal, aParams, aRv);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/xhr/XMLHttpRequest.h b/dom/xhr/XMLHttpRequest.h
new file mode 100644
index 000000000..a0dd0dbf6
--- /dev/null
+++ b/dom/xhr/XMLHttpRequest.h
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_XMLHttpRequest_h
+#define mozilla_dom_XMLHttpRequest_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/XMLHttpRequestEventTarget.h"
+#include "mozilla/dom/XMLHttpRequestBinding.h"
+#include "nsIXMLHttpRequest.h"
+
+class nsIJSID;
+
+namespace mozilla {
+namespace dom {
+
+class Blob;
+class DOMString;
+class FormData;
+class URLSearchParams;
+class XMLHttpRequestUpload;
+
+class XMLHttpRequest : public XMLHttpRequestEventTarget
+{
+public:
+ static already_AddRefed<XMLHttpRequest>
+ Constructor(const GlobalObject& aGlobal,
+ const MozXMLHttpRequestParameters& aParams,
+ ErrorResult& aRv);
+
+ static already_AddRefed<XMLHttpRequest>
+ Constructor(const GlobalObject& aGlobal, const nsAString& ignored,
+ ErrorResult& aRv)
+ {
+ // Pretend like someone passed null, so we can pick up the default values
+ MozXMLHttpRequestParameters params;
+ if (!params.Init(aGlobal.Context(), JS::NullHandleValue)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ return Constructor(aGlobal, params, aRv);
+ }
+
+ IMPL_EVENT_HANDLER(readystatechange)
+
+ virtual uint16_t
+ ReadyState() const = 0;
+
+ virtual void
+ Open(const nsACString& aMethod, const nsAString& aUrl, ErrorResult& aRv) = 0;
+
+ virtual void
+ Open(const nsACString& aMethod, const nsAString& aUrl, bool aAsync,
+ const nsAString& aUser, const nsAString& aPassword, ErrorResult& aRv) = 0;
+
+ virtual void
+ SetRequestHeader(const nsACString& aHeader, const nsACString& aValue,
+ ErrorResult& aRv) = 0;
+
+ virtual uint32_t
+ Timeout() const = 0;
+
+ virtual void
+ SetTimeout(uint32_t aTimeout, ErrorResult& aRv) = 0;
+
+ virtual bool
+ WithCredentials() const = 0;
+
+ virtual void
+ SetWithCredentials(bool aWithCredentials, ErrorResult& aRv) = 0;
+
+ virtual XMLHttpRequestUpload*
+ GetUpload(ErrorResult& aRv) = 0;
+
+ virtual void
+ Send(JSContext* aCx, ErrorResult& aRv) = 0;
+
+ virtual void
+ Send(JSContext* aCx, const ArrayBuffer& aArrayBuffer, ErrorResult& aRv) = 0;
+
+ virtual void
+ Send(JSContext* aCx, const ArrayBufferView& aArrayBufferView,
+ ErrorResult& aRv) = 0;
+
+ virtual void
+ Send(JSContext* aCx, Blob& aBlob, ErrorResult& aRv) = 0;
+
+ virtual void
+ Send(JSContext* aCx, URLSearchParams& aURLSearchParams, ErrorResult& aRv) = 0;
+
+ virtual void
+ Send(JSContext* aCx, nsIDocument& aDoc, ErrorResult& aRv) = 0;
+
+ virtual void
+ Send(JSContext* aCx, const nsAString& aString, ErrorResult& aRv) = 0;
+
+ virtual void
+ Send(JSContext* aCx, FormData& aFormData, ErrorResult& aRv) = 0;
+
+ virtual void
+ Send(JSContext* aCx, nsIInputStream* aStream, ErrorResult& aRv) = 0;
+
+ virtual void
+ Abort(ErrorResult& aRv) = 0;
+
+ virtual void
+ GetResponseURL(nsAString& aUrl) = 0;
+
+ virtual uint32_t
+ GetStatus(ErrorResult& aRv) = 0;
+
+ virtual void
+ GetStatusText(nsACString& aStatusText, ErrorResult& aRv) = 0;
+
+ virtual void
+ GetResponseHeader(const nsACString& aHeader, nsACString& aResult,
+ ErrorResult& aRv) = 0;
+
+ virtual void
+ GetAllResponseHeaders(nsACString& aResponseHeaders,
+ ErrorResult& aRv) = 0;
+
+ virtual void
+ OverrideMimeType(const nsAString& aMimeType, ErrorResult& aRv) = 0;
+
+ virtual XMLHttpRequestResponseType
+ ResponseType() const = 0;
+
+ virtual void
+ SetResponseType(XMLHttpRequestResponseType aType,
+ ErrorResult& aRv) = 0;
+
+ virtual void
+ GetResponse(JSContext* aCx, JS::MutableHandle<JS::Value> aResponse,
+ ErrorResult& aRv) = 0;
+
+ virtual void
+ GetResponseText(DOMString& aResponseText, ErrorResult& aRv) = 0;
+
+ virtual nsIDocument*
+ GetResponseXML(ErrorResult& aRv) = 0;
+
+ virtual bool
+ MozBackgroundRequest() const = 0;
+
+ virtual void
+ SetMozBackgroundRequest(bool aMozBackgroundRequest, ErrorResult& aRv) = 0;
+
+ virtual nsIChannel*
+ GetChannel() const = 0;
+
+ virtual void
+ GetNetworkInterfaceId(nsACString& aId) const = 0;
+
+ virtual void
+ SetNetworkInterfaceId(const nsACString& aId) = 0;
+
+ // We need a GetInterface callable from JS for chrome JS
+ virtual void
+ GetInterface(JSContext* aCx, nsIJSID* aIID,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv) = 0;
+
+ virtual void
+ SetOriginAttributes(const mozilla::dom::OriginAttributesDictionary& aAttrs) = 0;
+
+ virtual bool
+ MozAnon() const = 0;
+
+ virtual bool
+ MozSystem() const = 0;
+
+ virtual JSObject*
+ WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return mozilla::dom::XMLHttpRequestBinding::Wrap(aCx, this, aGivenProto);
+ }
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_XMLHttpRequest_h
diff --git a/dom/xhr/XMLHttpRequestEventTarget.cpp b/dom/xhr/XMLHttpRequestEventTarget.cpp
new file mode 100644
index 000000000..b6e65001a
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestEventTarget.cpp
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "XMLHttpRequestEventTarget.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestEventTarget)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestEventTarget,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestEventTarget,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XMLHttpRequestEventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequestEventTarget)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(XMLHttpRequestEventTarget, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(XMLHttpRequestEventTarget, DOMEventTargetHelper)
+
+void
+XMLHttpRequestEventTarget::DisconnectFromOwner()
+{
+ DOMEventTargetHelper::DisconnectFromOwner();
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/xhr/XMLHttpRequestEventTarget.h b/dom/xhr/XMLHttpRequestEventTarget.h
new file mode 100644
index 000000000..6f48d5758
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestEventTarget.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_XMLHttpRequestEventTarget_h
+#define mozilla_dom_XMLHttpRequestEventTarget_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsIXMLHttpRequest.h"
+
+namespace mozilla {
+namespace dom {
+
+class XMLHttpRequestEventTarget : public DOMEventTargetHelper,
+ public nsIXMLHttpRequestEventTarget
+{
+protected:
+ explicit XMLHttpRequestEventTarget(DOMEventTargetHelper* aOwner)
+ : DOMEventTargetHelper(aOwner)
+ {}
+
+ XMLHttpRequestEventTarget()
+ {}
+
+ virtual ~XMLHttpRequestEventTarget() {}
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XMLHttpRequestEventTarget,
+ DOMEventTargetHelper)
+ NS_DECL_NSIXMLHTTPREQUESTEVENTTARGET
+ NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
+
+ IMPL_EVENT_HANDLER(loadstart)
+ IMPL_EVENT_HANDLER(progress)
+ IMPL_EVENT_HANDLER(abort)
+ IMPL_EVENT_HANDLER(error)
+ IMPL_EVENT_HANDLER(load)
+ IMPL_EVENT_HANDLER(timeout)
+ IMPL_EVENT_HANDLER(loadend)
+
+ nsISupports* GetParentObject() const
+ {
+ return GetOwner();
+ }
+
+ virtual void DisconnectFromOwner() override;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_XMLHttpRequestEventTarget_h
diff --git a/dom/xhr/XMLHttpRequestMainThread.cpp b/dom/xhr/XMLHttpRequestMainThread.cpp
new file mode 100644
index 000000000..a47a01aa0
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -0,0 +1,4168 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "XMLHttpRequestMainThread.h"
+
+#include <algorithm>
+#ifndef XP_WIN
+#include <unistd.h>
+#endif
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/dom/BlobSet.h"
+#include "mozilla/dom/DOMString.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FetchUtil.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/MutableBlobStorage.h"
+#include "mozilla/dom/XMLDocument.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/LoadContext.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsIDOMDocument.h"
+#include "mozilla/dom/ProgressEvent.h"
+#include "nsIJARChannel.h"
+#include "nsIJARURI.h"
+#include "nsLayoutCID.h"
+#include "nsReadableUtils.h"
+
+#include "nsIURI.h"
+#include "nsILoadGroup.h"
+#include "nsNetUtil.h"
+#include "nsStringStream.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIOutputStream.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIUploadChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsIDOMSerializer.h"
+#include "nsXPCOM.h"
+#include "nsIDOMEventListener.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIVariant.h"
+#include "nsVariant.h"
+#include "nsIScriptError.h"
+#include "nsIStreamConverterService.h"
+#include "nsICachingChannel.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsError.h"
+#include "nsIHTMLDocument.h"
+#include "nsIStorageStream.h"
+#include "nsIPromptFactory.h"
+#include "nsIWindowWatcher.h"
+#include "nsIConsoleService.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "nsStringBuffer.h"
+#include "nsIFileChannel.h"
+#include "mozilla/Telemetry.h"
+#include "jsfriendapi.h"
+#include "GeckoProfiler.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "nsIUnicodeDecoder.h"
+#include "mozilla/dom/XMLHttpRequestBinding.h"
+#include "mozilla/Attributes.h"
+#include "MultipartBlobImpl.h"
+#include "nsIPermissionManager.h"
+#include "nsMimeTypes.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIClassOfService.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsStreamListenerWrapper.h"
+#include "xpcjsid.h"
+#include "nsITimedChannel.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsZipArchive.h"
+#include "mozilla/Preferences.h"
+#include "private/pprio.h"
+#include "XMLHttpRequestUpload.h"
+
+using namespace mozilla::net;
+
+namespace mozilla {
+namespace dom {
+
+// Maximum size that we'll grow an ArrayBuffer instead of doubling,
+// once doubling reaches this threshold
+const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH = 32*1024*1024;
+// start at 32k to avoid lots of doubling right at the start
+const uint32_t XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE = 32*1024;
+// the maximum Content-Length that we'll preallocate. 1GB. Must fit
+// in an int32_t!
+const int32_t XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE = 1*1024*1024*1024LL;
+
+namespace {
+ const nsLiteralString ProgressEventTypeStrings[] = {
+ NS_LITERAL_STRING("loadstart"),
+ NS_LITERAL_STRING("progress"),
+ NS_LITERAL_STRING("error"),
+ NS_LITERAL_STRING("abort"),
+ NS_LITERAL_STRING("timeout"),
+ NS_LITERAL_STRING("load"),
+ NS_LITERAL_STRING("loadend")
+ };
+ static_assert(MOZ_ARRAY_LENGTH(ProgressEventTypeStrings) ==
+ size_t(XMLHttpRequestMainThread::ProgressEventType::ENUM_MAX),
+ "Mismatched lengths for ProgressEventTypeStrings and ProgressEventType enums");
+
+ const nsString kLiteralString_readystatechange = NS_LITERAL_STRING("readystatechange");
+ const nsString kLiteralString_xmlhttprequest = NS_LITERAL_STRING("xmlhttprequest");
+ const nsString kLiteralString_DOMContentLoaded = NS_LITERAL_STRING("DOMContentLoaded");
+}
+
+// CIDs
+#define NS_BADCERTHANDLER_CONTRACTID \
+ "@mozilla.org/content/xmlhttprequest-bad-cert-handler;1"
+
+#define NS_PROGRESS_EVENT_INTERVAL 50
+#define MAX_SYNC_TIMEOUT_WHEN_UNLOADING 10000 /* 10 secs */
+
+NS_IMPL_ISUPPORTS(nsXHRParseEndListener, nsIDOMEventListener)
+
+class nsResumeTimeoutsEvent : public Runnable
+{
+public:
+ explicit nsResumeTimeoutsEvent(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) {}
+
+ NS_IMETHOD Run() override
+ {
+ mWindow->Resume();
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+};
+
+
+// This helper function adds the given load flags to the request's existing
+// load flags.
+static void AddLoadFlags(nsIRequest *request, nsLoadFlags newFlags)
+{
+ nsLoadFlags flags;
+ request->GetLoadFlags(&flags);
+ flags |= newFlags;
+ request->SetLoadFlags(flags);
+}
+
+/////////////////////////////////////////////
+//
+//
+/////////////////////////////////////////////
+
+bool
+XMLHttpRequestMainThread::sDontWarnAboutSyncXHR = false;
+
+XMLHttpRequestMainThread::XMLHttpRequestMainThread()
+ : mResponseBodyDecodedPos(0),
+ mResponseType(XMLHttpRequestResponseType::_empty),
+ mRequestObserver(nullptr),
+ mState(State::unsent),
+ mFlagSynchronous(false), mFlagAborted(false), mFlagParseBody(false),
+ mFlagSyncLooping(false), mFlagBackgroundRequest(false),
+ mFlagHadUploadListenersOnSend(false), mFlagACwithCredentials(false),
+ mFlagTimedOut(false), mFlagDeleted(false), mFlagSend(false),
+ mUploadTransferred(0), mUploadTotal(0), mUploadComplete(true),
+ mProgressSinceLastProgressEvent(false),
+ mRequestSentTime(0), mTimeoutMilliseconds(0),
+ mErrorLoad(false), mErrorParsingXML(false),
+ mWaitingForOnStopRequest(false),
+ mProgressTimerIsActive(false),
+ mIsHtml(false),
+ mWarnAboutSyncHtml(false),
+ mLoadTotal(-1),
+ mIsSystem(false),
+ mIsAnon(false),
+ mFirstStartRequestSeen(false),
+ mInLoadProgressEvent(false),
+ mResultJSON(JS::UndefinedValue()),
+ mResultArrayBuffer(nullptr),
+ mIsMappedArrayBuffer(false),
+ mXPCOMifier(nullptr)
+{
+ mozilla::HoldJSObjects(this);
+}
+
+XMLHttpRequestMainThread::~XMLHttpRequestMainThread()
+{
+ mFlagDeleted = true;
+
+ if ((mState == State::opened && mFlagSend) ||
+ mState == State::loading) {
+ Abort();
+ }
+
+ MOZ_ASSERT(!mFlagSyncLooping, "we rather crash than hang");
+ mFlagSyncLooping = false;
+
+ mResultJSON.setUndefined();
+ mResultArrayBuffer = nullptr;
+ mozilla::DropJSObjects(this);
+}
+
+/**
+ * This Init method is called from the factory constructor.
+ */
+nsresult
+XMLHttpRequestMainThread::Init()
+{
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ nsCOMPtr<nsIPrincipal> subjectPrincipal;
+ if (secMan) {
+ secMan->GetSystemPrincipal(getter_AddRefs(subjectPrincipal));
+ }
+ NS_ENSURE_STATE(subjectPrincipal);
+
+ // Instead of grabbing some random global from the context stack,
+ // let's use the default one (junk scope) for now.
+ // We should move away from this Init...
+ Construct(subjectPrincipal, xpc::NativeGlobal(xpc::PrivilegedJunkScope()));
+ return NS_OK;
+}
+
+/**
+ * This Init method should only be called by C++ consumers.
+ */
+NS_IMETHODIMP
+XMLHttpRequestMainThread::Init(nsIPrincipal* aPrincipal,
+ nsIGlobalObject* aGlobalObject,
+ nsIURI* aBaseURI,
+ nsILoadGroup* aLoadGroup)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ Construct(aPrincipal, aGlobalObject, aBaseURI, aLoadGroup);
+ return NS_OK;
+}
+
+void
+XMLHttpRequestMainThread::InitParameters(bool aAnon, bool aSystem)
+{
+ if (!aAnon && !aSystem) {
+ return;
+ }
+
+ // Check for permissions.
+ // Chrome is always allowed access, so do the permission check only
+ // for non-chrome pages.
+ if (!IsSystemXHR() && aSystem) {
+ nsIGlobalObject* global = GetOwnerGlobal();
+ if (NS_WARN_IF(!global)) {
+ SetParameters(aAnon, false);
+ return;
+ }
+
+ nsIPrincipal* principal = global->PrincipalOrNull();
+ if (NS_WARN_IF(!principal)) {
+ SetParameters(aAnon, false);
+ return;
+ }
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ services::GetPermissionManager();
+ if (NS_WARN_IF(!permMgr)) {
+ SetParameters(aAnon, false);
+ return;
+ }
+
+ uint32_t permission;
+ nsresult rv =
+ permMgr->TestPermissionFromPrincipal(principal, "systemXHR", &permission);
+ if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) {
+ SetParameters(aAnon, false);
+ return;
+ }
+ }
+
+ SetParameters(aAnon, aSystem);
+}
+
+void
+XMLHttpRequestMainThread::ResetResponse()
+{
+ mResponseXML = nullptr;
+ mResponseBody.Truncate();
+ TruncateResponseText();
+ mResponseBlob = nullptr;
+ mDOMBlob = nullptr;
+ mBlobStorage = nullptr;
+ mBlobSet = nullptr;
+ mResultArrayBuffer = nullptr;
+ mArrayBufferBuilder.reset();
+ mResultJSON.setUndefined();
+ mDataAvailable = 0;
+ mLoadTransferred = 0;
+ mResponseBodyDecodedPos = 0;
+}
+
+void
+XMLHttpRequestMainThread::SetRequestObserver(nsIRequestObserver* aObserver)
+{
+ mRequestObserver = aObserver;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestMainThread)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(XMLHttpRequestMainThread)
+ bool isBlack = tmp->IsBlack();
+ if (isBlack || tmp->mWaitingForOnStopRequest) {
+ if (tmp->mListenerManager) {
+ tmp->mListenerManager->MarkForCC();
+ }
+ if (!isBlack && tmp->PreservingWrapper()) {
+ // This marks the wrapper black.
+ tmp->GetWrapper();
+ }
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(XMLHttpRequestMainThread)
+ return tmp->
+ IsBlackAndDoesNotNeedTracing(static_cast<DOMEventTargetHelper*>(tmp));
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(XMLHttpRequestMainThread)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestMainThread,
+ XMLHttpRequestEventTarget)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseXML)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXMLParserStreamListener)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponseBlob)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMBlob)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationCallbacks)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannelEventSink)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressEventSink)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestMainThread,
+ XMLHttpRequestEventTarget)
+ tmp->mResultArrayBuffer = nullptr;
+ tmp->mArrayBufferBuilder.reset();
+ tmp->mResultJSON.setUndefined();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseXML)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mXMLParserStreamListener)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponseBlob)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMBlob)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationCallbacks)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannelEventSink)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressEventSink)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestMainThread,
+ XMLHttpRequestEventTarget)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultJSON)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+// QueryInterface implementation for XMLHttpRequestMainThread
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XMLHttpRequestMainThread)
+ NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIJSXMLHttpRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsISizeOfEventTarget)
+NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
+
+NS_IMPL_ADDREF_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
+NS_IMPL_RELEASE_INHERITED(XMLHttpRequestMainThread, XMLHttpRequestEventTarget)
+
+NS_IMPL_EVENT_HANDLER(XMLHttpRequestMainThread, readystatechange)
+
+void
+XMLHttpRequestMainThread::DisconnectFromOwner()
+{
+ XMLHttpRequestEventTarget::DisconnectFromOwner();
+ Abort();
+}
+
+size_t
+XMLHttpRequestMainThread::SizeOfEventTargetIncludingThis(
+ MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+ n += mResponseBody.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+
+ // Why is this safe? Because no-one else will report this string. The
+ // other possible sharers of this string are as follows.
+ //
+ // - The JS engine could hold copies if the JS code holds references, e.g.
+ // |var text = XHR.responseText|. However, those references will be via JS
+ // external strings, for which the JS memory reporter does *not* report the
+ // chars.
+ //
+ // - Binary extensions, but they're *extremely* unlikely to do any memory
+ // reporting.
+ //
+ n += mResponseText.SizeOfThis(aMallocSizeOf);
+
+ return n;
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - lots
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetChannel(nsIChannel **aChannel)
+{
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_IF_ADDREF(*aChannel = mChannel);
+
+ return NS_OK;
+}
+
+static void LogMessage(const char* aWarning, nsPIDOMWindowInner* aWindow,
+ const char16_t** aParams=nullptr, uint32_t aParamCount=0)
+{
+ nsCOMPtr<nsIDocument> doc;
+ if (aWindow) {
+ doc = aWindow->GetExtantDoc();
+ }
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"), doc,
+ nsContentUtils::eDOM_PROPERTIES,
+ aWarning, aParams, aParamCount);
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetResponseXML(nsIDOMDocument **aResponseXML)
+{
+ ErrorResult rv;
+ nsIDocument* responseXML = GetResponseXML(rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ if (!responseXML) {
+ *aResponseXML = nullptr;
+ return NS_OK;
+ }
+
+ return CallQueryInterface(responseXML, aResponseXML);
+}
+
+nsIDocument*
+XMLHttpRequestMainThread::GetResponseXML(ErrorResult& aRv)
+{
+ if (mResponseType != XMLHttpRequestResponseType::_empty &&
+ mResponseType != XMLHttpRequestResponseType::Document) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSEXML);
+ return nullptr;
+ }
+ if (mWarnAboutSyncHtml) {
+ mWarnAboutSyncHtml = false;
+ LogMessage("HTMLSyncXHRWarning", GetOwner());
+ }
+ if (mState != State::done) {
+ return nullptr;
+ }
+ return mResponseXML;
+}
+
+/*
+ * This piece copied from XMLDocument, we try to get the charset
+ * from HTTP headers.
+ */
+nsresult
+XMLHttpRequestMainThread::DetectCharset()
+{
+ mResponseCharset.Truncate();
+ mDecoder = nullptr;
+
+ if (mResponseType != XMLHttpRequestResponseType::_empty &&
+ mResponseType != XMLHttpRequestResponseType::Text &&
+ mResponseType != XMLHttpRequestResponseType::Json &&
+ mResponseType != XMLHttpRequestResponseType::Moz_chunked_text) {
+ return NS_OK;
+ }
+
+ nsAutoCString charsetVal;
+ bool ok = mChannel &&
+ NS_SUCCEEDED(mChannel->GetContentCharset(charsetVal)) &&
+ EncodingUtils::FindEncodingForLabel(charsetVal, mResponseCharset);
+ if (!ok || mResponseCharset.IsEmpty()) {
+ // MS documentation states UTF-8 is default for responseText
+ mResponseCharset.AssignLiteral("UTF-8");
+ }
+
+ if (mResponseType == XMLHttpRequestResponseType::Json &&
+ !mResponseCharset.EqualsLiteral("UTF-8")) {
+ // The XHR spec says only UTF-8 is supported for responseType == "json"
+ LogMessage("JSONCharsetWarning", GetOwner());
+ mResponseCharset.AssignLiteral("UTF-8");
+ }
+
+ mDecoder = EncodingUtils::DecoderForEncoding(mResponseCharset);
+
+ return NS_OK;
+}
+
+nsresult
+XMLHttpRequestMainThread::AppendToResponseText(const char * aSrcBuffer,
+ uint32_t aSrcBufferLen)
+{
+ NS_ENSURE_STATE(mDecoder);
+
+ int32_t destBufferLen;
+ nsresult rv = mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen,
+ &destBufferLen);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CheckedInt32 size = mResponseText.Length();
+ size += destBufferLen;
+ if (!size.isValid()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ XMLHttpRequestStringWriterHelper helper(mResponseText);
+
+ if (!helper.AddCapacity(destBufferLen)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // This code here is basically a copy of a similar thing in
+ // nsScanner::Append(const char* aBuffer, uint32_t aLen).
+ int32_t srclen = (int32_t)aSrcBufferLen;
+ int32_t destlen = (int32_t)destBufferLen;
+ rv = mDecoder->Convert(aSrcBuffer,
+ &srclen,
+ helper.EndOfExistingData(),
+ &destlen);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(destlen <= destBufferLen);
+
+ helper.AddLength(destlen);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetResponseText(nsAString& aResponseText)
+{
+ ErrorResult rv;
+ DOMString str;
+ GetResponseText(str, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ str.ToString(aResponseText);
+ return NS_OK;
+}
+
+void
+XMLHttpRequestMainThread::GetResponseText(DOMString& aResponseText,
+ ErrorResult& aRv)
+{
+ XMLHttpRequestStringSnapshot snapshot;
+ GetResponseText(snapshot, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (!snapshot.GetAsString(aResponseText)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+}
+
+void
+XMLHttpRequestMainThread::GetResponseText(XMLHttpRequestStringSnapshot& aSnapshot,
+ ErrorResult& aRv)
+{
+ aSnapshot.Reset();
+
+ if (mResponseType != XMLHttpRequestResponseType::_empty &&
+ mResponseType != XMLHttpRequestResponseType::Text &&
+ mResponseType != XMLHttpRequestResponseType::Moz_chunked_text) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSETEXT);
+ return;
+ }
+
+ if (mResponseType == XMLHttpRequestResponseType::Moz_chunked_text &&
+ !mInLoadProgressEvent) {
+ aSnapshot.SetVoid();
+ return;
+ }
+
+ if (mState != State::loading && mState != State::done) {
+ return;
+ }
+
+ // We only decode text lazily if we're also parsing to a doc.
+ // Also, if we've decoded all current data already, then no need to decode
+ // more.
+ if ((!mResponseXML && !mErrorParsingXML) ||
+ mResponseBodyDecodedPos == mResponseBody.Length()) {
+ mResponseText.CreateSnapshot(aSnapshot);
+ return;
+ }
+
+ MatchCharsetAndDecoderToResponseDocument();
+
+ NS_ASSERTION(mResponseBodyDecodedPos < mResponseBody.Length(),
+ "Unexpected mResponseBodyDecodedPos");
+ aRv = AppendToResponseText(mResponseBody.get() + mResponseBodyDecodedPos,
+ mResponseBody.Length() - mResponseBodyDecodedPos);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ mResponseBodyDecodedPos = mResponseBody.Length();
+
+ if (mState == State::done) {
+ // Free memory buffer which we no longer need
+ mResponseBody.Truncate();
+ mResponseBodyDecodedPos = 0;
+ }
+
+ mResponseText.CreateSnapshot(aSnapshot);
+}
+
+nsresult
+XMLHttpRequestMainThread::CreateResponseParsedJSON(JSContext* aCx)
+{
+ if (!aCx) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString string;
+ if (!mResponseText.GetAsString(string)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // The Unicode converter has already zapped the BOM if there was one
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS_ParseJSON(aCx, string.BeginReading(), string.Length(), &value)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mResultJSON = value;
+ return NS_OK;
+}
+
+void
+XMLHttpRequestMainThread::CreatePartialBlob(ErrorResult& aRv)
+{
+ if (mDOMBlob) {
+ // Use progress info to determine whether load is complete, but use
+ // mDataAvailable to ensure a slice is created based on the uncompressed
+ // data count.
+ if (mState == State::done) {
+ mResponseBlob = mDOMBlob;
+ } else {
+ mResponseBlob = mDOMBlob->CreateSlice(0, mDataAvailable,
+ EmptyString(), aRv);
+ }
+ return;
+ }
+
+ // mBlobSet can be null if the request has been canceled
+ if (!mBlobSet) {
+ return;
+ }
+
+ nsAutoCString contentType;
+ if (mState == State::done) {
+ mChannel->GetContentType(contentType);
+ }
+
+ nsTArray<RefPtr<BlobImpl>> subImpls(mBlobSet->GetBlobImpls());
+ RefPtr<BlobImpl> blobImpl =
+ MultipartBlobImpl::Create(Move(subImpls),
+ NS_ConvertASCIItoUTF16(contentType),
+ aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ mResponseBlob = Blob::Create(GetOwner(), blobImpl);
+}
+
+NS_IMETHODIMP XMLHttpRequestMainThread::GetResponseType(nsAString& aResponseType)
+{
+ MOZ_ASSERT(mResponseType < XMLHttpRequestResponseType::EndGuard_);
+ const EnumEntry& entry =
+ XMLHttpRequestResponseTypeValues::strings[static_cast<uint32_t>(mResponseType)];
+ aResponseType.AssignASCII(entry.value, entry.length);
+ return NS_OK;
+}
+
+NS_IMETHODIMP XMLHttpRequestMainThread::SetResponseType(const nsAString& aResponseType)
+{
+ uint32_t i = 0;
+ for (const EnumEntry* entry = XMLHttpRequestResponseTypeValues::strings;
+ entry->value; ++entry, ++i) {
+ if (aResponseType.EqualsASCII(entry->value, entry->length)) {
+ ErrorResult rv;
+ SetResponseType(static_cast<XMLHttpRequestResponseType>(i), rv);
+ return rv.StealNSResult();
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+XMLHttpRequestMainThread::SetResponseType(XMLHttpRequestResponseType aResponseType,
+ ErrorResult& aRv)
+{
+ if (mState == State::loading || mState == State::done) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_LOADING_OR_DONE);
+ return;
+ }
+
+ // sync request is not allowed setting responseType in window context
+ if (HasOrHasHadOwner() && mState != State::unsent && mFlagSynchronous) {
+ LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC);
+ return;
+ }
+
+ if (mFlagSynchronous &&
+ (aResponseType == XMLHttpRequestResponseType::Moz_chunked_text ||
+ aResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_CHUNKED_RESPONSETYPES_UNSUPPORTED_FOR_SYNC);
+ return;
+ }
+
+ // We want to get rid of this moz-only types. Bug 1335365.
+ if (aResponseType == XMLHttpRequestResponseType::Moz_blob) {
+ Telemetry::Accumulate(Telemetry::MOZ_BLOB_IN_XHR, 1);
+ } else if (aResponseType == XMLHttpRequestResponseType::Moz_chunked_text) {
+ Telemetry::Accumulate(Telemetry::MOZ_CHUNKED_TEXT_IN_XHR, 1);
+ } else if (aResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer) {
+ Telemetry::Accumulate(Telemetry::MOZ_CHUNKED_ARRAYBUFFER_IN_XHR, 1);
+ }
+
+ // Set the responseType attribute's value to the given value.
+ mResponseType = aResponseType;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetResponse(JSContext *aCx, JS::MutableHandle<JS::Value> aResult)
+{
+ ErrorResult rv;
+ GetResponse(aCx, aResult, rv);
+ return rv.StealNSResult();
+}
+
+void
+XMLHttpRequestMainThread::GetResponse(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResponse,
+ ErrorResult& aRv)
+{
+ switch (mResponseType) {
+ case XMLHttpRequestResponseType::_empty:
+ case XMLHttpRequestResponseType::Text:
+ case XMLHttpRequestResponseType::Moz_chunked_text:
+ {
+ DOMString str;
+ GetResponseText(str, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ if (!xpc::StringToJsval(aCx, str, aResponse)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ }
+ return;
+ }
+
+ case XMLHttpRequestResponseType::Arraybuffer:
+ case XMLHttpRequestResponseType::Moz_chunked_arraybuffer:
+ {
+ if (!(mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
+ mState == State::done) &&
+ !(mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer &&
+ mInLoadProgressEvent)) {
+ aResponse.setNull();
+ return;
+ }
+
+ if (!mResultArrayBuffer) {
+ mResultArrayBuffer = mArrayBufferBuilder.getArrayBuffer(aCx);
+ if (!mResultArrayBuffer) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+ aResponse.setObject(*mResultArrayBuffer);
+ return;
+ }
+ case XMLHttpRequestResponseType::Blob:
+ case XMLHttpRequestResponseType::Moz_blob:
+ {
+ if (mState != State::done) {
+ if (mResponseType != XMLHttpRequestResponseType::Moz_blob) {
+ aResponse.setNull();
+ return;
+ }
+
+ if (!mResponseBlob) {
+ CreatePartialBlob(aRv);
+ }
+ }
+
+ if (!mResponseBlob) {
+ aResponse.setNull();
+ return;
+ }
+
+ GetOrCreateDOMReflector(aCx, mResponseBlob, aResponse);
+ return;
+ }
+ case XMLHttpRequestResponseType::Document:
+ {
+ if (!mResponseXML || mState != State::done) {
+ aResponse.setNull();
+ return;
+ }
+
+ aRv = nsContentUtils::WrapNative(aCx, mResponseXML, aResponse);
+ return;
+ }
+ case XMLHttpRequestResponseType::Json:
+ {
+ if (mState != State::done) {
+ aResponse.setNull();
+ return;
+ }
+
+ if (mResultJSON.isUndefined()) {
+ aRv = CreateResponseParsedJSON(aCx);
+ TruncateResponseText();
+ if (aRv.Failed()) {
+ // Per spec, errors aren't propagated. null is returned instead.
+ aRv = NS_OK;
+ // It would be nice to log the error to the console. That's hard to
+ // do without calling window.onerror as a side effect, though.
+ JS_ClearPendingException(aCx);
+ mResultJSON.setNull();
+ }
+ }
+ aResponse.set(mResultJSON);
+ return;
+ }
+ default:
+ NS_ERROR("Should not happen");
+ }
+
+ aResponse.setNull();
+}
+
+bool
+XMLHttpRequestMainThread::IsCrossSiteCORSRequest() const
+{
+ if (!mChannel) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ return loadInfo->GetTainting() == LoadTainting::CORS;
+}
+
+bool
+XMLHttpRequestMainThread::IsDeniedCrossSiteCORSRequest()
+{
+ if (IsCrossSiteCORSRequest()) {
+ nsresult rv;
+ mChannel->GetStatus(&rv);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+XMLHttpRequestMainThread::GetResponseURL(nsAString& aUrl)
+{
+ aUrl.Truncate();
+
+ uint16_t readyState = ReadyState();
+ if ((readyState == UNSENT || readyState == OPENED) || !mChannel) {
+ return;
+ }
+
+ // Make sure we don't leak responseURL information from denied cross-site
+ // requests.
+ if (IsDeniedCrossSiteCORSRequest()) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> responseUrl;
+ mChannel->GetURI(getter_AddRefs(responseUrl));
+
+ if (!responseUrl) {
+ return;
+ }
+
+ nsAutoCString temp;
+ responseUrl->GetSpecIgnoringRef(temp);
+ CopyUTF8toUTF16(temp, aUrl);
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetStatus(uint32_t *aStatus)
+{
+ ErrorResult rv;
+ *aStatus = GetStatus(rv);
+ return rv.StealNSResult();
+}
+
+uint32_t
+XMLHttpRequestMainThread::GetStatus(ErrorResult& aRv)
+{
+ // Make sure we don't leak status information from denied cross-site
+ // requests.
+ if (IsDeniedCrossSiteCORSRequest()) {
+ return 0;
+ }
+
+ uint16_t readyState = ReadyState();
+ if (readyState == UNSENT || readyState == OPENED) {
+ return 0;
+ }
+
+ if (mErrorLoad) {
+ // Let's simulate the http protocol for jar/app requests:
+ nsCOMPtr<nsIJARChannel> jarChannel = GetCurrentJARChannel();
+ if (jarChannel) {
+ nsresult status;
+ mChannel->GetStatus(&status);
+
+ if (status == NS_ERROR_FILE_NOT_FOUND) {
+ return 404; // Not Found
+ } else {
+ return 500; // Internal Error
+ }
+ }
+
+ return 0;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
+ if (!httpChannel) {
+ // Pretend like we got a 200 response, since our load was successful
+ return 200;
+ }
+
+ uint32_t status;
+ nsresult rv = httpChannel->GetResponseStatus(&status);
+ if (NS_FAILED(rv)) {
+ status = 0;
+ }
+
+ return status;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetStatusText(nsACString& aOut)
+{
+ ErrorResult rv;
+ GetStatusText(aOut, rv);
+ return rv.StealNSResult();
+}
+
+void
+XMLHttpRequestMainThread::GetStatusText(nsACString& aStatusText,
+ ErrorResult& aRv)
+{
+ // Return an empty status text on all error loads.
+ aStatusText.Truncate();
+
+ // Make sure we don't leak status information from denied cross-site
+ // requests.
+ if (IsDeniedCrossSiteCORSRequest()) {
+ return;
+ }
+
+ // Check the current XHR state to see if it is valid to obtain the statusText
+ // value. This check is to prevent the status text for redirects from being
+ // available before all the redirects have been followed and HTTP headers have
+ // been received.
+ uint16_t readyState = ReadyState();
+ if (readyState == UNSENT || readyState == OPENED) {
+ return;
+ }
+
+ if (mErrorLoad) {
+ return;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
+ if (httpChannel) {
+ httpChannel->GetResponseStatusText(aStatusText);
+ } else {
+ aStatusText.AssignLiteral("OK");
+ }
+}
+
+void
+XMLHttpRequestMainThread::CloseRequest()
+{
+ mWaitingForOnStopRequest = false;
+ if (mChannel) {
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ }
+ if (mTimeoutTimer) {
+ mTimeoutTimer->Cancel();
+ }
+}
+
+void
+XMLHttpRequestMainThread::CloseRequestWithError(const ProgressEventType aType)
+{
+ CloseRequest();
+
+ ResetResponse();
+
+ // If we're in the destructor, don't risk dispatching an event.
+ if (mFlagDeleted) {
+ mFlagSyncLooping = false;
+ return;
+ }
+
+ if (mState != State::unsent &&
+ !(mState == State::opened && !mFlagSend) &&
+ mState != State::done) {
+ ChangeState(State::done, true);
+
+ if (!mFlagSyncLooping) {
+ if (mUpload && !mUploadComplete) {
+ mUploadComplete = true;
+ DispatchProgressEvent(mUpload, aType, 0, -1);
+ }
+ DispatchProgressEvent(this, aType, 0, -1);
+ }
+ }
+
+ // The ChangeState call above calls onreadystatechange handlers which
+ // if they load a new url will cause XMLHttpRequestMainThread::Open to clear
+ // the abort state bit. If this occurs we're not uninitialized (bug 361773).
+ if (mFlagAborted) {
+ ChangeState(State::unsent, false); // IE seems to do it
+ }
+
+ mFlagSyncLooping = false;
+}
+
+void
+XMLHttpRequestMainThread::Abort(ErrorResult& arv)
+{
+ mFlagAborted = true;
+ CloseRequestWithError(ProgressEventType::abort);
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::SlowAbort()
+{
+ Abort();
+ return NS_OK;
+}
+
+/*Method that checks if it is safe to expose a header value to the client.
+It is used to check what headers are exposed for CORS requests.*/
+bool
+XMLHttpRequestMainThread::IsSafeHeader(const nsACString& aHeader,
+ NotNull<nsIHttpChannel*> aHttpChannel) const
+{
+ // See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
+ if (!IsSystemXHR() && nsContentUtils::IsForbiddenResponseHeader(aHeader)) {
+ NS_WARNING("blocked access to response header");
+ return false;
+ }
+ // if this is not a CORS call all headers are safe
+ if (!IsCrossSiteCORSRequest()) {
+ return true;
+ }
+ // Check for dangerous headers
+ // Make sure we don't leak header information from denied cross-site
+ // requests.
+ if (mChannel) {
+ nsresult status;
+ mChannel->GetStatus(&status);
+ if (NS_FAILED(status)) {
+ return false;
+ }
+ }
+ const char* kCrossOriginSafeHeaders[] = {
+ "cache-control", "content-language", "content-type", "expires",
+ "last-modified", "pragma"
+ };
+ for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
+ if (aHeader.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
+ return true;
+ }
+ }
+ nsAutoCString headerVal;
+ // The "Access-Control-Expose-Headers" header contains a comma separated
+ // list of method names.
+ aHttpChannel->
+ GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"),
+ headerVal);
+ nsCCharSeparatedTokenizer exposeTokens(headerVal, ',');
+ bool isSafe = false;
+ while (exposeTokens.hasMoreTokens()) {
+ const nsDependentCSubstring& token = exposeTokens.nextToken();
+ if (token.IsEmpty()) {
+ continue;
+ }
+ if (!NS_IsValidHTTPToken(token)) {
+ return false;
+ }
+ if (aHeader.Equals(token, nsCaseInsensitiveCStringComparator())) {
+ isSafe = true;
+ }
+ }
+ return isSafe;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetAllResponseHeaders(nsACString& aOut)
+{
+ ErrorResult rv;
+ GetAllResponseHeaders(aOut, rv);
+ return rv.StealNSResult();
+}
+
+void
+XMLHttpRequestMainThread::GetAllResponseHeaders(nsACString& aResponseHeaders,
+ ErrorResult& aRv)
+{
+ aResponseHeaders.Truncate();
+
+ // If the state is UNSENT or OPENED,
+ // return the empty string and terminate these steps.
+ if (mState == State::unsent || mState == State::opened) {
+ return;
+ }
+
+ if (mErrorLoad) {
+ return;
+ }
+
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel()) {
+ RefPtr<nsHeaderVisitor> visitor =
+ new nsHeaderVisitor(*this, WrapNotNull(httpChannel));
+ if (NS_SUCCEEDED(httpChannel->VisitResponseHeaders(visitor))) {
+ aResponseHeaders = visitor->Headers();
+ }
+ return;
+ }
+
+ if (!mChannel) {
+ return;
+ }
+
+ // Even non-http channels supply content type.
+ nsAutoCString value;
+ if (NS_SUCCEEDED(mChannel->GetContentType(value))) {
+ aResponseHeaders.AppendLiteral("Content-Type: ");
+ aResponseHeaders.Append(value);
+ if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) && !value.IsEmpty()) {
+ aResponseHeaders.AppendLiteral(";charset=");
+ aResponseHeaders.Append(value);
+ }
+ aResponseHeaders.AppendLiteral("\r\n");
+ }
+
+ // Don't provide Content-Length for data URIs
+ nsCOMPtr<nsIURI> uri;
+ bool isDataURI;
+ if (NS_FAILED(mChannel->GetURI(getter_AddRefs(uri))) ||
+ NS_FAILED(uri->SchemeIs("data", &isDataURI)) ||
+ !isDataURI) {
+ int64_t length;
+ if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
+ aResponseHeaders.AppendLiteral("Content-Length: ");
+ aResponseHeaders.AppendInt(length);
+ aResponseHeaders.AppendLiteral("\r\n");
+ }
+ }
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetResponseHeader(const nsACString& aHeader,
+ nsACString& aResult)
+{
+ ErrorResult rv;
+ GetResponseHeader(aHeader, aResult, rv);
+ return rv.StealNSResult();
+}
+
+void
+XMLHttpRequestMainThread::GetResponseHeader(const nsACString& header,
+ nsACString& _retval, ErrorResult& aRv)
+{
+ _retval.SetIsVoid(true);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
+
+ if (!httpChannel) {
+ // If the state is UNSENT or OPENED,
+ // return null and terminate these steps.
+ if (mState == State::unsent || mState == State::opened) {
+ return;
+ }
+
+ // Even non-http channels supply content type and content length.
+ // Remember we don't leak header information from denied cross-site
+ // requests.
+ nsresult status;
+ if (!mChannel ||
+ NS_FAILED(mChannel->GetStatus(&status)) ||
+ NS_FAILED(status)) {
+ return;
+ }
+
+ // Content Type:
+ if (header.LowerCaseEqualsASCII("content-type")) {
+ if (NS_FAILED(mChannel->GetContentType(_retval))) {
+ // Means no content type
+ _retval.SetIsVoid(true);
+ return;
+ }
+
+ nsCString value;
+ if (NS_SUCCEEDED(mChannel->GetContentCharset(value)) &&
+ !value.IsEmpty()) {
+ _retval.AppendLiteral(";charset=");
+ _retval.Append(value);
+ }
+ }
+
+ // Content Length:
+ else if (header.LowerCaseEqualsASCII("content-length")) {
+ int64_t length;
+ if (NS_SUCCEEDED(mChannel->GetContentLength(&length))) {
+ _retval.AppendInt(length);
+ }
+ }
+
+ return;
+ }
+
+ // Check for dangerous headers
+ if (!IsSafeHeader(header, WrapNotNull(httpChannel))) {
+ return;
+ }
+
+ aRv = httpChannel->GetResponseHeader(header, _retval);
+ if (aRv.ErrorCodeIs(NS_ERROR_NOT_AVAILABLE)) {
+ // Means no header
+ _retval.SetIsVoid(true);
+ aRv.SuppressException();
+ }
+}
+
+already_AddRefed<nsILoadGroup>
+XMLHttpRequestMainThread::GetLoadGroup() const
+{
+ if (mFlagBackgroundRequest) {
+ return nullptr;
+ }
+
+ if (mLoadGroup) {
+ nsCOMPtr<nsILoadGroup> ref = mLoadGroup;
+ return ref.forget();
+ }
+
+ nsIDocument* doc = GetDocumentIfCurrent();
+ if (doc) {
+ return doc->GetDocumentLoadGroup();
+ }
+
+ return nullptr;
+}
+
+nsresult
+XMLHttpRequestMainThread::FireReadystatechangeEvent()
+{
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+ event->InitEvent(kLiteralString_readystatechange, false, false);
+ // We assume anyone who managed to call CreateReadystatechangeEvent is trusted
+ event->SetTrusted(true);
+ DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+ return NS_OK;
+}
+
+void
+XMLHttpRequestMainThread::DispatchProgressEvent(DOMEventTargetHelper* aTarget,
+ const ProgressEventType aType,
+ int64_t aLoaded, int64_t aTotal)
+{
+ NS_ASSERTION(aTarget, "null target");
+
+ if (NS_FAILED(CheckInnerWindowCorrectness()) ||
+ (!AllowUploadProgress() && aTarget == mUpload)) {
+ return;
+ }
+
+ // If blocked by CORS, zero-out the stats on progress events
+ // and never fire "progress" or "load" events at all.
+ if (IsDeniedCrossSiteCORSRequest()) {
+ if (aType == ProgressEventType::progress ||
+ aType == ProgressEventType::load) {
+ return;
+ }
+ aLoaded = 0;
+ aTotal = -1;
+ }
+
+ if (aType == ProgressEventType::progress) {
+ mInLoadProgressEvent = true;
+ }
+
+ ProgressEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mLengthComputable = aTotal != -1; // XHR spec step 6.1
+ init.mLoaded = aLoaded;
+ init.mTotal = (aTotal == -1) ? 0 : aTotal;
+
+ const nsAString& typeString = ProgressEventTypeStrings[(uint8_t)aType];
+ RefPtr<ProgressEvent> event =
+ ProgressEvent::Constructor(aTarget, typeString, init);
+ event->SetTrusted(true);
+
+ aTarget->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+
+ if (aType == ProgressEventType::progress) {
+ mInLoadProgressEvent = false;
+
+ // clear chunked responses after every progress event
+ if (mResponseType == XMLHttpRequestResponseType::Moz_chunked_text ||
+ mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer) {
+ mResponseBody.Truncate();
+ TruncateResponseText();
+ mResultArrayBuffer = nullptr;
+ mArrayBufferBuilder.reset();
+ }
+ }
+
+ // If we're sending a load, error, timeout or abort event, then
+ // also dispatch the subsequent loadend event.
+ if (aType == ProgressEventType::load || aType == ProgressEventType::error ||
+ aType == ProgressEventType::timeout || aType == ProgressEventType::abort) {
+ DispatchProgressEvent(aTarget, ProgressEventType::loadend, aLoaded, aTotal);
+ }
+}
+
+already_AddRefed<nsIHttpChannel>
+XMLHttpRequestMainThread::GetCurrentHttpChannel()
+{
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ return httpChannel.forget();
+}
+
+already_AddRefed<nsIJARChannel>
+XMLHttpRequestMainThread::GetCurrentJARChannel()
+{
+ nsCOMPtr<nsIJARChannel> appChannel = do_QueryInterface(mChannel);
+ return appChannel.forget();
+}
+
+bool
+XMLHttpRequestMainThread::IsSystemXHR() const
+{
+ return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal);
+}
+
+bool
+XMLHttpRequestMainThread::InUploadPhase() const
+{
+ // We're in the upload phase while our state is State::opened.
+ return mState == State::opened;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::Open(const nsACString& aMethod, const nsACString& aUrl,
+ bool aAsync, const nsAString& aUsername,
+ const nsAString& aPassword, uint8_t optional_argc)
+{
+ return Open(aMethod, aUrl, optional_argc > 0 ? aAsync : true,
+ aUsername, aPassword);
+}
+
+// This case is hit when the async parameter is outright omitted, which
+// should set it to true (and the username and password to null).
+void
+XMLHttpRequestMainThread::Open(const nsACString& aMethod, const nsAString& aUrl,
+ ErrorResult& aRv)
+{
+ Open(aMethod, aUrl, true, NullString(), NullString(), aRv);
+}
+
+// This case is hit when the async parameter is specified, even if the
+// JS value was "undefined" (which due to legacy reasons should be
+// treated as true, which is how it will already be passed in here).
+void
+XMLHttpRequestMainThread::Open(const nsACString& aMethod,
+ const nsAString& aUrl,
+ bool aAsync,
+ const nsAString& aUsername,
+ const nsAString& aPassword,
+ ErrorResult& aRv)
+{
+ nsresult rv = Open(aMethod, NS_ConvertUTF16toUTF8(aUrl), aAsync, aUsername, aPassword);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+nsresult
+XMLHttpRequestMainThread::Open(const nsACString& aMethod,
+ const nsACString& aUrl,
+ bool aAsync,
+ const nsAString& aUsername,
+ const nsAString& aPassword) {
+ // Gecko-specific
+ if (!aAsync && !DontWarnAboutSyncXHR() && GetOwner() &&
+ GetOwner()->GetExtantDoc()) {
+ GetOwner()->GetExtantDoc()->WarnOnceAbout(nsIDocument::eSyncXMLHttpRequest);
+ }
+
+ Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC, aAsync ? 0 : 1);
+
+ // Step 1
+ nsCOMPtr<nsIDocument> responsibleDocument = GetDocumentIfCurrent();
+ if (!responsibleDocument) {
+ // This could be because we're no longer current or because we're in some
+ // non-window context...
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
+ }
+ }
+ NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
+
+ // Steps 2-4
+ nsAutoCString method;
+ nsresult rv = FetchUtil::GetValidRequestMethod(aMethod, method);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Steps 5-6
+ nsCOMPtr<nsIURI> baseURI;
+ if (mBaseURI) {
+ baseURI = mBaseURI;
+ } else if (responsibleDocument) {
+ baseURI = responsibleDocument->GetBaseURI();
+ }
+ nsCOMPtr<nsIURI> parsedURL;
+ rv = NS_NewURI(getter_AddRefs(parsedURL), aUrl, nullptr, baseURI);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ return NS_ERROR_DOM_MALFORMED_URI;
+ }
+ return rv;
+ }
+ if (NS_WARN_IF(NS_FAILED(CheckInnerWindowCorrectness()))) {
+ return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
+ }
+
+ // Step 7
+ // This is already handled by the other Open() method, which passes
+ // username and password in as NullStrings.
+
+ // Step 8
+ nsAutoCString host;
+ parsedURL->GetHost(host);
+ if (!host.IsEmpty()) {
+ nsAutoCString userpass;
+ if (!aUsername.IsVoid()) {
+ CopyUTF16toUTF8(aUsername, userpass);
+ }
+ userpass.AppendLiteral(":");
+ if (!aPassword.IsVoid()) {
+ AppendUTF16toUTF8(aPassword, userpass);
+ }
+ parsedURL->SetUserPass(userpass);
+ }
+
+ // Step 9
+ if (!aAsync && HasOrHasHadOwner() && (mTimeoutMilliseconds ||
+ mResponseType != XMLHttpRequestResponseType::_empty)) {
+ if (mTimeoutMilliseconds) {
+ LogMessage("TimeoutSyncXHRWarning", GetOwner());
+ }
+ if (mResponseType != XMLHttpRequestResponseType::_empty) {
+ LogMessage("ResponseTypeSyncXHRWarning", GetOwner());
+ }
+ return NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC;
+ }
+
+ // Step 10
+ CloseRequest();
+
+ // Step 11
+ // timeouts are handled without a flag
+ mFlagSend = false;
+ mRequestMethod.Assign(method);
+ mRequestURL = parsedURL;
+ mFlagSynchronous = !aAsync;
+ mAuthorRequestHeaders.Clear();
+ ResetResponse();
+
+ // Gecko-specific
+ mFlagHadUploadListenersOnSend = false;
+ mFlagAborted = false;
+ mFlagTimedOut = false;
+
+ // Per spec we should only create the channel on send(), but we have internal
+ // code that relies on the channel being created now, and that code is not
+ // always IsSystemXHR(). However, we're not supposed to throw channel-creation
+ // errors during open(), so we silently ignore those here.
+ CreateChannel();
+
+ // Step 12
+ if (mState != State::opened) {
+ mState = State::opened;
+ FireReadystatechangeEvent();
+ }
+
+ return NS_OK;
+}
+
+void
+XMLHttpRequestMainThread::SetOriginAttributes(const OriginAttributesDictionary& aAttrs)
+{
+ MOZ_ASSERT((mState == State::opened) && !mFlagSend);
+
+ GenericOriginAttributes attrs(aAttrs);
+ NeckoOriginAttributes neckoAttrs;
+ neckoAttrs.SetFromGenericAttributes(attrs);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ MOZ_ASSERT(loadInfo);
+ loadInfo->SetOriginAttributes(neckoAttrs);
+}
+
+void
+XMLHttpRequestMainThread::PopulateNetworkInterfaceId()
+{
+ if (mNetworkInterfaceId.IsEmpty()) {
+ return;
+ }
+ nsCOMPtr<nsIHttpChannelInternal> channel(do_QueryInterface(mChannel));
+ if (!channel) {
+ return;
+ }
+ channel->SetNetworkInterfaceId(mNetworkInterfaceId);
+}
+
+/*
+ * "Copy" from a stream.
+ */
+nsresult
+XMLHttpRequestMainThread::StreamReaderFunc(nsIInputStream* in,
+ void* closure,
+ const char* fromRawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount)
+{
+ XMLHttpRequestMainThread* xmlHttpRequest = static_cast<XMLHttpRequestMainThread*>(closure);
+ if (!xmlHttpRequest || !writeCount) {
+ NS_WARNING("XMLHttpRequest cannot read from stream: no closure or writeCount");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = NS_OK;
+
+ if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Blob) {
+ if (!xmlHttpRequest->mDOMBlob) {
+ xmlHttpRequest->MaybeCreateBlobStorage();
+ rv = xmlHttpRequest->mBlobStorage->Append(fromRawSegment, count);
+ }
+ } else if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Moz_blob) {
+ if (!xmlHttpRequest->mDOMBlob) {
+ if (!xmlHttpRequest->mBlobSet) {
+ xmlHttpRequest->mBlobSet = new BlobSet();
+ }
+ rv = xmlHttpRequest->mBlobSet->AppendVoidPtr(fromRawSegment, count);
+ }
+ // Clear the cache so that the blob size is updated.
+ xmlHttpRequest->mResponseBlob = nullptr;
+ } else if ((xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
+ !xmlHttpRequest->mIsMappedArrayBuffer) ||
+ xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer) {
+ // get the initial capacity to something reasonable to avoid a bunch of reallocs right
+ // at the start
+ if (xmlHttpRequest->mArrayBufferBuilder.capacity() == 0)
+ xmlHttpRequest->mArrayBufferBuilder.setCapacity(std::max(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE));
+
+ xmlHttpRequest->mArrayBufferBuilder.append(reinterpret_cast<const uint8_t*>(fromRawSegment), count,
+ XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH);
+ } else if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::_empty &&
+ xmlHttpRequest->mResponseXML) {
+ // Copy for our own use
+ if (!xmlHttpRequest->mResponseBody.Append(fromRawSegment, count, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else if (xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::_empty ||
+ xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Text ||
+ xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Json ||
+ xmlHttpRequest->mResponseType == XMLHttpRequestResponseType::Moz_chunked_text) {
+ NS_ASSERTION(!xmlHttpRequest->mResponseXML,
+ "We shouldn't be parsing a doc here");
+ xmlHttpRequest->AppendToResponseText(fromRawSegment, count);
+ }
+
+ if (xmlHttpRequest->mFlagParseBody) {
+ // Give the same data to the parser.
+
+ // We need to wrap the data in a new lightweight stream and pass that
+ // to the parser, because calling ReadSegments() recursively on the same
+ // stream is not supported.
+ nsCOMPtr<nsIInputStream> copyStream;
+ rv = NS_NewByteInputStream(getter_AddRefs(copyStream), fromRawSegment, count);
+
+ if (NS_SUCCEEDED(rv) && xmlHttpRequest->mXMLParserStreamListener) {
+ NS_ASSERTION(copyStream, "NS_NewByteInputStream lied");
+ nsresult parsingResult = xmlHttpRequest->mXMLParserStreamListener
+ ->OnDataAvailable(xmlHttpRequest->mChannel,
+ xmlHttpRequest->mContext,
+ copyStream, toOffset, count);
+
+ // No use to continue parsing if we failed here, but we
+ // should still finish reading the stream
+ if (NS_FAILED(parsingResult)) {
+ xmlHttpRequest->mFlagParseBody = false;
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ *writeCount = count;
+ } else {
+ *writeCount = 0;
+ }
+
+ return rv;
+}
+
+bool XMLHttpRequestMainThread::CreateDOMBlob(nsIRequest *request)
+{
+ nsCOMPtr<nsIFile> file;
+ nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(request);
+ if (fc) {
+ fc->GetFile(getter_AddRefs(file));
+ }
+
+ if (!file)
+ return false;
+
+ nsAutoCString contentType;
+ mChannel->GetContentType(contentType);
+
+ mDOMBlob = File::CreateFromFile(GetOwner(), file, EmptyString(),
+ NS_ConvertASCIItoUTF16(contentType));
+
+ mBlobStorage = nullptr;
+ mBlobSet = nullptr;
+ NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
+ return true;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::OnDataAvailable(nsIRequest *request,
+ nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset,
+ uint32_t count)
+{
+ NS_ENSURE_ARG_POINTER(inStr);
+
+ MOZ_ASSERT(mContext.get() == ctxt,"start context different from OnDataAvailable context");
+
+ mProgressSinceLastProgressEvent = true;
+ XMLHttpRequestBinding::ClearCachedResponseTextValue(this);
+
+ bool cancelable = false;
+ if ((mResponseType == XMLHttpRequestResponseType::Blob ||
+ mResponseType == XMLHttpRequestResponseType::Moz_blob) && !mDOMBlob) {
+ cancelable = CreateDOMBlob(request);
+ // The nsIStreamListener contract mandates us
+ // to read from the stream before returning.
+ }
+
+ uint32_t totalRead;
+ nsresult rv = inStr->ReadSegments(XMLHttpRequestMainThread::StreamReaderFunc,
+ (void*)this, count, &totalRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (cancelable) {
+ // We don't have to read from the local file for the blob response
+ ErrorResult error;
+ mDataAvailable = mDOMBlob->GetSize(error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ ChangeState(State::loading);
+ return request->Cancel(NS_OK);
+ }
+
+ mDataAvailable += totalRead;
+
+ // Fire the first progress event/loading state change
+ if (mState != State::loading) {
+ ChangeState(State::loading);
+ if (!mFlagSynchronous) {
+ DispatchProgressEvent(this, ProgressEventType::progress,
+ mLoadTransferred, mLoadTotal);
+ }
+ mProgressSinceLastProgressEvent = false;
+ }
+
+ if (!mFlagSynchronous && !mProgressTimerIsActive) {
+ StartProgressEventTimer();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ PROFILER_LABEL("XMLHttpRequestMainThread", "OnStartRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ nsresult rv = NS_OK;
+ if (!mFirstStartRequestSeen && mRequestObserver) {
+ mFirstStartRequestSeen = true;
+ mRequestObserver->OnStartRequest(request, ctxt);
+ }
+
+ if (request != mChannel) {
+ // Can this still happen?
+ return NS_OK;
+ }
+
+ // Don't do anything if we have been aborted
+ if (mState == State::unsent) {
+ return NS_OK;
+ }
+
+ /* Apparently, Abort() should set State::unsent. See bug 361773.
+ XHR2 spec says this is correct. */
+ if (mFlagAborted) {
+ NS_ERROR("Ugh, still getting data on an aborted XMLHttpRequest!");
+
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Don't do anything if we have timed out.
+ if (mFlagTimedOut) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
+ NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
+
+ nsresult status;
+ request->GetStatus(&status);
+ mErrorLoad = mErrorLoad || NS_FAILED(status);
+
+ // Upload phase is now over. If we were uploading anything,
+ // stop the timer and fire any final progress events.
+ if (mUpload && !mUploadComplete && !mErrorLoad && !mFlagSynchronous) {
+ StopProgressEventTimer();
+
+ mUploadTransferred = mUploadTotal;
+
+ if (mProgressSinceLastProgressEvent) {
+ DispatchProgressEvent(mUpload, ProgressEventType::progress,
+ mUploadTransferred, mUploadTotal);
+ mProgressSinceLastProgressEvent = false;
+ }
+
+ mUploadComplete = true;
+ DispatchProgressEvent(mUpload, ProgressEventType::load,
+ mUploadTotal, mUploadTotal);
+ }
+
+ mContext = ctxt;
+ mFlagParseBody = true;
+ ChangeState(State::headers_received);
+
+ ResetResponse();
+
+ if (!mOverrideMimeType.IsEmpty()) {
+ channel->SetContentType(NS_ConvertUTF16toUTF8(mOverrideMimeType));
+ }
+
+ DetectCharset();
+
+ // Set up arraybuffer
+ if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
+ NS_SUCCEEDED(status)) {
+ if (mIsMappedArrayBuffer) {
+ nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
+ if (jarChannel) {
+ nsCOMPtr<nsIURI> uri;
+ rv = channel->GetURI(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString file;
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ if (scheme.LowerCaseEqualsLiteral("jar")) {
+ nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri);
+ if (jarURI) {
+ jarURI->GetJAREntry(file);
+ }
+ }
+ nsCOMPtr<nsIFile> jarFile;
+ jarChannel->GetJarFile(getter_AddRefs(jarFile));
+ if (!jarFile) {
+ mIsMappedArrayBuffer = false;
+ } else {
+ rv = mArrayBufferBuilder.mapToFileInPackage(file, jarFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mIsMappedArrayBuffer = false;
+ } else {
+ channel->SetContentType(NS_LITERAL_CSTRING("application/mem-mapped"));
+ }
+ }
+ }
+ }
+ }
+ // If memory mapping failed, mIsMappedArrayBuffer would be set to false,
+ // and we want it fallback to the malloc way.
+ if (!mIsMappedArrayBuffer) {
+ int64_t contentLength;
+ rv = channel->GetContentLength(&contentLength);
+ if (NS_SUCCEEDED(rv) &&
+ contentLength > 0 &&
+ contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) {
+ mArrayBufferBuilder.setCapacity(static_cast<int32_t>(contentLength));
+ }
+ }
+ }
+
+ // Set up responseXML
+ bool parseBody = mResponseType == XMLHttpRequestResponseType::_empty ||
+ mResponseType == XMLHttpRequestResponseType::Document;
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
+ if (parseBody && httpChannel) {
+ nsAutoCString method;
+ httpChannel->GetRequestMethod(method);
+ parseBody = !method.EqualsLiteral("HEAD");
+ }
+
+ mIsHtml = false;
+ mWarnAboutSyncHtml = false;
+ if (parseBody && NS_SUCCEEDED(status)) {
+ // We can gain a huge performance win by not even trying to
+ // parse non-XML data. This also protects us from the situation
+ // where we have an XML document and sink, but HTML (or other)
+ // parser, which can produce unreliable results.
+ nsAutoCString type;
+ channel->GetContentType(type);
+
+ if ((mResponseType == XMLHttpRequestResponseType::Document) &&
+ type.EqualsLiteral("text/html")) {
+ // HTML parsing is only supported for responseType == "document" to
+ // avoid running the parser and, worse, populating responseXML for
+ // legacy users of XHR who use responseType == "" for retrieving the
+ // responseText of text/html resources. This legacy case is so common
+ // that it's not useful to emit a warning about it.
+ if (mFlagSynchronous) {
+ // We don't make cool new features available in the bad synchronous
+ // mode. The synchronous mode is for legacy only.
+ mWarnAboutSyncHtml = true;
+ mFlagParseBody = false;
+ } else {
+ mIsHtml = true;
+ }
+ } else if (!(type.EqualsLiteral("text/xml") ||
+ type.EqualsLiteral("application/xml") ||
+ type.RFind("+xml", true, -1, 4) != kNotFound)) {
+ // Follow https://xhr.spec.whatwg.org/
+ // If final MIME type is not null, text/html, text/xml, application/xml,
+ // or does not end in +xml, return null.
+ mFlagParseBody = false;
+ }
+ } else {
+ // The request failed, so we shouldn't be parsing anyway
+ mFlagParseBody = false;
+ }
+
+ if (mFlagParseBody) {
+ nsCOMPtr<nsIURI> baseURI, docURI;
+ rv = mChannel->GetURI(getter_AddRefs(docURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ baseURI = docURI;
+
+ nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
+ nsCOMPtr<nsIURI> chromeXHRDocURI, chromeXHRDocBaseURI;
+ if (doc) {
+ chromeXHRDocURI = doc->GetDocumentURI();
+ chromeXHRDocBaseURI = doc->GetBaseURI();
+ } else {
+ // If we're no longer current, just kill the load, though it really should
+ // have been killed already.
+ if (NS_WARN_IF(NS_FAILED(CheckInnerWindowCorrectness()))) {
+ return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
+ }
+ }
+
+ // Create an empty document from it.
+ const nsAString& emptyStr = EmptyString();
+ nsCOMPtr<nsIDOMDocument> responseDoc;
+ nsIGlobalObject* global = DOMEventTargetHelper::GetParentObject();
+
+ nsCOMPtr<nsIPrincipal> requestingPrincipal;
+ rv = nsContentUtils::GetSecurityManager()->
+ GetChannelResultPrincipal(channel, getter_AddRefs(requestingPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewDOMDocument(getter_AddRefs(responseDoc),
+ emptyStr, emptyStr, nullptr, docURI,
+ baseURI, requestingPrincipal, true, global,
+ mIsHtml ? DocumentFlavorHTML :
+ DocumentFlavorLegacyGuess);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mResponseXML = do_QueryInterface(responseDoc);
+ mResponseXML->SetChromeXHRDocURI(chromeXHRDocURI);
+ mResponseXML->SetChromeXHRDocBaseURI(chromeXHRDocBaseURI);
+
+ if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
+ mResponseXML->ForceEnableXULXBL();
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ MOZ_ASSERT(loadInfo);
+ bool isCrossSite = loadInfo->GetTainting() != LoadTainting::Basic;
+
+ if (isCrossSite) {
+ nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mResponseXML);
+ if (htmlDoc) {
+ htmlDoc->DisableCookieAccess();
+ }
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ channel->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ // suppress <parsererror> nodes on XML document parse failure, but only
+ // for non-privileged code (including Web Extensions). See bug 289714.
+ if (!IsSystemXHR()) {
+ mResponseXML->SetSuppressParserErrorElement(true);
+ }
+
+ rv = mResponseXML->StartDocumentLoad(kLoadAsData, channel, loadGroup,
+ nullptr, getter_AddRefs(listener),
+ !isCrossSite);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // the spec requires the response document.referrer to be the empty string
+ mResponseXML->SetReferrer(NS_LITERAL_CSTRING(""));
+
+ mXMLParserStreamListener = listener;
+ rv = mXMLParserStreamListener->OnStartRequest(request, ctxt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Download phase beginning; start the progress event timer if necessary.
+ if (NS_SUCCEEDED(rv) && HasListenersFor(nsGkAtoms::onprogress)) {
+ StartProgressEventTimer();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
+{
+ PROFILER_LABEL("XMLHttpRequestMainThread", "OnStopRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ if (request != mChannel) {
+ // Can this still happen?
+ return NS_OK;
+ }
+
+ mWaitingForOnStopRequest = false;
+
+ if (mRequestObserver) {
+ NS_ASSERTION(mFirstStartRequestSeen, "Inconsistent state!");
+ mFirstStartRequestSeen = false;
+ mRequestObserver->OnStopRequest(request, ctxt, status);
+ }
+
+ // suppress parsing failure messages to console for status 204/304 (see bug 884693).
+ if (mResponseXML) {
+ uint32_t responseStatus;
+ if (NS_SUCCEEDED(GetStatus(&responseStatus)) &&
+ (responseStatus == 204 || responseStatus == 304)) {
+ mResponseXML->SetSuppressParserErrorConsoleMessages(true);
+ }
+ }
+
+ // make sure to notify the listener if we were aborted
+ // XXX in fact, why don't we do the cleanup below in this case??
+ // State::unsent is for abort calls. See OnStartRequest above.
+ if (mState == State::unsent || mFlagTimedOut) {
+ if (mXMLParserStreamListener)
+ (void) mXMLParserStreamListener->OnStopRequest(request, ctxt, status);
+ return NS_OK;
+ }
+
+ // Is this good enough here?
+ if (mXMLParserStreamListener && mFlagParseBody) {
+ mXMLParserStreamListener->OnStopRequest(request, ctxt, status);
+ }
+
+ mXMLParserStreamListener = nullptr;
+ mContext = nullptr;
+
+ bool waitingForBlobCreation = false;
+
+ if (NS_SUCCEEDED(status) &&
+ (mResponseType == XMLHttpRequestResponseType::Blob ||
+ mResponseType == XMLHttpRequestResponseType::Moz_blob)) {
+ ErrorResult rv;
+ if (!mDOMBlob) {
+ CreateDOMBlob(request);
+ }
+ if (mDOMBlob) {
+ mResponseBlob = mDOMBlob;
+ mDOMBlob = nullptr;
+ } else {
+ // Smaller files may be written in cache map instead of separate files.
+ // Also, no-store response cannot be written in persistent cache.
+ nsAutoCString contentType;
+ mChannel->GetContentType(contentType);
+
+ if (mResponseType == XMLHttpRequestResponseType::Blob) {
+ // mBlobStorage can be null if the channel is non-file non-cacheable
+ // and if the response length is zero.
+ MaybeCreateBlobStorage();
+ mBlobStorage->GetBlobWhenReady(GetOwner(), contentType, this);
+ waitingForBlobCreation = true;
+ } else {
+ // mBlobSet can be null if the channel is non-file non-cacheable
+ // and if the response length is zero.
+ if (!mBlobSet) {
+ mBlobSet = new BlobSet();
+ }
+
+ nsTArray<RefPtr<BlobImpl>> subImpls(mBlobSet->GetBlobImpls());
+ RefPtr<BlobImpl> blobImpl =
+ MultipartBlobImpl::Create(Move(subImpls),
+ NS_ConvertASCIItoUTF16(contentType),
+ rv);
+ mBlobSet = nullptr;
+
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ mResponseBlob = Blob::Create(GetOwner(), blobImpl);
+ }
+ }
+
+ NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
+ NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
+ } else if (NS_SUCCEEDED(status) &&
+ ((mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
+ !mIsMappedArrayBuffer) ||
+ mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer)) {
+ // set the capacity down to the actual length, to realloc back
+ // down to the actual size
+ if (!mArrayBufferBuilder.setCapacity(mArrayBufferBuilder.length())) {
+ // this should never happen!
+ status = NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
+ NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
+
+ channel->SetNotificationCallbacks(nullptr);
+ mNotificationCallbacks = nullptr;
+ mChannelEventSink = nullptr;
+ mProgressEventSink = nullptr;
+
+ mFlagSyncLooping = false;
+
+ // update our charset and decoder to match mResponseXML,
+ // before it is possibly nulled out
+ MatchCharsetAndDecoderToResponseDocument();
+
+ if (NS_FAILED(status)) {
+ // This can happen if the server is unreachable. Other possible
+ // reasons are that the user leaves the page or hits the ESC key.
+
+ mErrorLoad = true;
+ mResponseXML = nullptr;
+ }
+
+ // If we're uninitialized at this point, we encountered an error
+ // earlier and listeners have already been notified. Also we do
+ // not want to do this if we already completed.
+ if (mState == State::unsent || mState == State::done) {
+ return NS_OK;
+ }
+
+ if (!mResponseXML) {
+ mFlagParseBody = false;
+
+ //We postpone the 'done' until the creation of the Blob is completed.
+ if (!waitingForBlobCreation) {
+ ChangeStateToDone();
+ }
+
+ return NS_OK;
+ }
+
+ if (mIsHtml) {
+ NS_ASSERTION(!mFlagSyncLooping,
+ "We weren't supposed to support HTML parsing with XHR!");
+ nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(mResponseXML);
+ EventListenerManager* manager =
+ eventTarget->GetOrCreateListenerManager();
+ manager->AddEventListenerByType(new nsXHRParseEndListener(this),
+ kLiteralString_DOMContentLoaded,
+ TrustedEventsAtSystemGroupBubble());
+ return NS_OK;
+ } else {
+ mFlagParseBody = false;
+ }
+
+ // We might have been sent non-XML data. If that was the case,
+ // we should null out the document member. The idea in this
+ // check here is that if there is no document element it is not
+ // an XML document. We might need a fancier check...
+ if (!mResponseXML->GetRootElement()) {
+ mErrorParsingXML = true;
+ mResponseXML = nullptr;
+ }
+ ChangeStateToDone();
+ return NS_OK;
+}
+
+void
+XMLHttpRequestMainThread::OnBodyParseEnd()
+{
+ mFlagParseBody = false;
+ ChangeStateToDone();
+}
+
+void
+XMLHttpRequestMainThread::MatchCharsetAndDecoderToResponseDocument()
+{
+ if (mResponseXML && mResponseCharset != mResponseXML->GetDocumentCharacterSet()) {
+ mResponseCharset = mResponseXML->GetDocumentCharacterSet();
+ TruncateResponseText();
+ mResponseBodyDecodedPos = 0;
+ mDecoder = EncodingUtils::DecoderForEncoding(mResponseCharset);
+ }
+}
+
+void
+XMLHttpRequestMainThread::ChangeStateToDone()
+{
+ StopProgressEventTimer();
+
+ MOZ_ASSERT(!mFlagParseBody,
+ "ChangeStateToDone() called before async HTML parsing is done.");
+
+ mFlagSend = false;
+
+ if (mTimeoutTimer) {
+ mTimeoutTimer->Cancel();
+ }
+
+ // Per spec, fire the last download progress event, if any,
+ // before readystatechange=4/done. (Note that 0-sized responses
+ // will have not sent a progress event yet, so one must be sent here).
+ if (!mFlagSynchronous &&
+ (!mLoadTransferred || mProgressSinceLastProgressEvent)) {
+ DispatchProgressEvent(this, ProgressEventType::progress,
+ mLoadTransferred, mLoadTotal);
+ mProgressSinceLastProgressEvent = false;
+ }
+
+ // Per spec, fire readystatechange=4/done before final error events.
+ ChangeState(State::done, true);
+
+ // Per spec, if we failed in the upload phase, fire a final error
+ // and loadend events for the upload after readystatechange=4/done.
+ if (!mFlagSynchronous && mUpload && !mUploadComplete) {
+ DispatchProgressEvent(mUpload, ProgressEventType::error, 0, -1);
+ }
+
+ // Per spec, fire download's load/error and loadend events after
+ // readystatechange=4/done (and of course all upload events).
+ DispatchProgressEvent(this,
+ mErrorLoad ? ProgressEventType::error :
+ ProgressEventType::load,
+ mErrorLoad ? 0 : mLoadTransferred,
+ mErrorLoad ? -1 : mLoadTotal);
+
+ if (mErrorLoad) {
+ // By nulling out channel here we make it so that Send() can test
+ // for that and throw. Also calling the various status
+ // methods/members will not throw.
+ // This matches what IE does.
+ mChannel = nullptr;
+ }
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<nsIDocument>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset) const
+{
+ nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(mBody));
+ NS_ENSURE_STATE(domdoc);
+ aCharset.AssignLiteral("UTF-8");
+
+ nsresult rv;
+ nsCOMPtr<nsIStorageStream> storStream;
+ rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> output;
+ rv = storStream->GetOutputStream(0, getter_AddRefs(output));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mBody->IsHTMLDocument()) {
+ aContentType.AssignLiteral("text/html");
+
+ nsString serialized;
+ if (!nsContentUtils::SerializeNodeToMarkup(mBody, true, serialized)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAutoCString utf8Serialized;
+ if (!AppendUTF16toUTF8(serialized, utf8Serialized, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t written;
+ rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(written == utf8Serialized.Length());
+ } else {
+ aContentType.AssignLiteral("application/xml");
+
+ nsCOMPtr<nsIDOMSerializer> serializer =
+ do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure to use the encoding we'll send
+ rv = serializer->SerializeToStream(domdoc, output, aCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ output->Close();
+
+ uint32_t length;
+ rv = storStream->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aContentLength = length;
+
+ rv = storStream->NewInputStream(0, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<const nsAString>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset) const
+{
+ aContentType.AssignLiteral("text/plain");
+ aCharset.AssignLiteral("UTF-8");
+
+ nsAutoCString converted;
+ if (!AppendUTF16toUTF8(*mBody, converted, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *aContentLength = converted.Length();
+ nsresult rv = NS_NewCStringInputStream(aResult, converted);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<nsIInputStream>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset) const
+{
+ aContentType.AssignLiteral("text/plain");
+ aCharset.Truncate();
+
+ nsresult rv = mBody->Available(aContentLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream(mBody);
+ stream.forget(aResult);
+ return NS_OK;
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<Blob>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset) const
+{
+ return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<FormData>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset) const
+{
+ return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<URLSearchParams>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset) const
+{
+ return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<nsIXHRSendable>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset) const
+{
+ return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
+}
+
+static nsresult
+GetBufferDataAsStream(const uint8_t* aData, uint32_t aDataLength,
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset)
+{
+ aContentType.SetIsVoid(true);
+ aCharset.Truncate();
+
+ *aContentLength = aDataLength;
+ const char* data = reinterpret_cast<const char*>(aData);
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength,
+ NS_ASSIGNMENT_COPY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ stream.forget(aResult);
+
+ return NS_OK;
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<const ArrayBuffer>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset) const
+{
+ mBody->ComputeLengthAndData();
+ return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
+ aResult, aContentLength, aContentType, aCharset);
+}
+
+template<> nsresult
+XMLHttpRequestMainThread::RequestBody<const ArrayBufferView>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset) const
+{
+ mBody->ComputeLengthAndData();
+ return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
+ aResult, aContentLength, aContentType, aCharset);
+}
+
+
+nsresult
+XMLHttpRequestMainThread::CreateChannel()
+{
+ // When we are called from JS we can find the load group for the page,
+ // and add ourselves to it. This way any pending requests
+ // will be automatically aborted if the user leaves the page.
+ nsCOMPtr<nsILoadGroup> loadGroup = GetLoadGroup();
+
+ nsSecurityFlags secFlags;
+ nsLoadFlags loadFlags = nsIRequest::LOAD_BACKGROUND |
+ nsIChannel::LOAD_CLASSIFY_URI;
+ if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
+ // When chrome is loading we want to make sure to sandbox any potential
+ // result document. We also want to allow cross-origin loads.
+ secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL |
+ nsILoadInfo::SEC_SANDBOXED;
+ } else if (IsSystemXHR()) {
+ // For pages that have appropriate permissions, we want to still allow
+ // cross-origin loads, but make sure that the any potential result
+ // documents get the same principal as the loader.
+ secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ } else {
+ // Otherwise use CORS. Again, make sure that potential result documents
+ // use the same principal as the loader.
+ secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS |
+ nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ if (mIsAnon) {
+ secFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
+ }
+
+ // Use the responsibleDocument if we have it, except for dedicated workers
+ // where it will be the parent document, which is not the one we want to use.
+ nsresult rv;
+ nsCOMPtr<nsIDocument> responsibleDocument = GetDocumentIfCurrent();
+ if (responsibleDocument && responsibleDocument->NodePrincipal() == mPrincipal) {
+ rv = NS_NewChannel(getter_AddRefs(mChannel),
+ mRequestURL,
+ responsibleDocument,
+ secFlags,
+ nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
+ loadGroup,
+ nullptr, // aCallbacks
+ loadFlags);
+ } else {
+ // Otherwise use the principal.
+ rv = NS_NewChannel(getter_AddRefs(mChannel),
+ mRequestURL,
+ mPrincipal,
+ secFlags,
+ nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
+ loadGroup,
+ nullptr, // aCallbacks
+ loadFlags);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
+ if (httpChannel) {
+ rv = httpChannel->SetRequestMethod(mRequestMethod);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set the initiator type
+ nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
+ if (timedChannel) {
+ timedChannel->SetInitiatorType(NS_LITERAL_STRING("xmlhttprequest"));
+ }
+ }
+
+ // Using the provided principal as the triggeringPrincipal is fine, since we
+ // want to be able to access any of the origins that the principal has access
+ // to during the security checks, but we don't want a document to inherit an
+ // expanded principal, so in that case we need to select the principal in the
+ // expanded principal's whitelist that can load our URL as principalToInherit.
+ nsCOMPtr<nsIPrincipal> resultingDocumentPrincipal(mPrincipal);
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(mPrincipal);
+ if (ep) {
+ nsTArray<nsCOMPtr<nsIPrincipal>>* whitelist = nullptr;
+ ep->GetWhiteList(&whitelist);
+ if (!whitelist) {
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_ASSERT(!(secFlags & nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS));
+ bool dataInherits = (secFlags &
+ (nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS)) != 0;
+ for (const auto& principal : *whitelist) {
+ if (NS_SUCCEEDED(principal->CheckMayLoad(mRequestURL, false, dataInherits))) {
+ resultingDocumentPrincipal = principal;
+ break;
+ }
+ }
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ rv = loadInfo->SetPrincipalToInherit(resultingDocumentPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+XMLHttpRequestMainThread::InitiateFetch(nsIInputStream* aUploadStream,
+ int64_t aUploadLength,
+ nsACString& aUploadContentType)
+{
+ nsresult rv;
+
+ // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which
+ // in turn keeps STOP button from becoming active. If the consumer passed in
+ // a progress event handler we must load with nsIRequest::LOAD_NORMAL or
+ // necko won't generate any progress notifications.
+ if (HasListenersFor(nsGkAtoms::onprogress) ||
+ (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress))) {
+ nsLoadFlags loadFlags;
+ mChannel->GetLoadFlags(&loadFlags);
+ loadFlags &= ~nsIRequest::LOAD_BACKGROUND;
+ loadFlags |= nsIRequest::LOAD_NORMAL;
+ mChannel->SetLoadFlags(loadFlags);
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
+ if (httpChannel) {
+ // If the user hasn't overridden the Accept header, set it to */* per spec.
+ if (!mAuthorRequestHeaders.Has("accept")) {
+ mAuthorRequestHeaders.Set("accept", NS_LITERAL_CSTRING("*/*"));
+ }
+
+ mAuthorRequestHeaders.ApplyToChannel(httpChannel);
+
+ if (!IsSystemXHR()) {
+ nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
+ nsCOMPtr<nsIDocument> doc = owner ? owner->GetExtantDoc() : nullptr;
+ nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal, doc,
+ httpChannel,
+ mozilla::net::RP_Default);
+ }
+
+ // Some extensions override the http protocol handler and provide their own
+ // implementation. The channels returned from that implementation don't
+ // always seem to implement the nsIUploadChannel2 interface, presumably
+ // because it's a new interface. Eventually we should remove this and simply
+ // require that http channels implement the new interface (see bug 529041).
+ nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
+ if (!uploadChannel2) {
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ consoleService->LogStringMessage(NS_LITERAL_STRING(
+ "Http channel implementation doesn't support nsIUploadChannel2. "
+ "An extension has supplied a non-functional http protocol handler. "
+ "This will break behavior and in future releases not work at all."
+ ).get());
+ }
+ }
+
+ if (aUploadStream) {
+ // If necessary, wrap the stream in a buffered stream so as to guarantee
+ // support for our upload when calling ExplicitSetUploadStream.
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ if (!NS_InputStreamIsBuffered(aUploadStream)) {
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
+ aUploadStream, 4096);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aUploadStream = bufferedStream;
+ }
+
+ // We want to use a newer version of the upload channel that won't
+ // ignore the necessary headers for an empty Content-Type.
+ nsCOMPtr<nsIUploadChannel2> uploadChannel2(do_QueryInterface(httpChannel));
+ // This assertion will fire if buggy extensions are installed
+ NS_ASSERTION(uploadChannel2, "http must support nsIUploadChannel2");
+ if (uploadChannel2) {
+ uploadChannel2->ExplicitSetUploadStream(aUploadStream,
+ aUploadContentType,
+ mUploadTotal, mRequestMethod,
+ false);
+ } else {
+ // The http channel doesn't support the new nsIUploadChannel2.
+ // Emulate it as best we can using nsIUploadChannel.
+ if (aUploadContentType.IsEmpty()) {
+ aUploadContentType.AssignLiteral("application/octet-stream");
+ }
+ nsCOMPtr<nsIUploadChannel> uploadChannel =
+ do_QueryInterface(httpChannel);
+ uploadChannel->SetUploadStream(aUploadStream, aUploadContentType,
+ mUploadTotal);
+ // Reset the method to its original value
+ httpChannel->SetRequestMethod(mRequestMethod);
+ }
+ }
+ }
+
+ // Due to the chrome-only XHR.channel API, we need a hacky way to set the
+ // SEC_COOKIES_INCLUDE *after* the channel has been has been created, since
+ // .withCredentials can be called after open() is called.
+ // Not doing this for privileged system XHRs since those don't use CORS.
+ if (!IsSystemXHR() && !mIsAnon && mFlagACwithCredentials) {
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ static_cast<net::LoadInfo*>(loadInfo.get())->SetIncludeCookiesSecFlag();
+ }
+
+ // Blocking gets are common enough out of XHR that we should mark
+ // the channel slow by default for pipeline purposes
+ AddLoadFlags(mChannel, nsIRequest::INHIBIT_PIPELINE);
+
+ // We never let XHR be blocked by head CSS/JS loads to avoid potential
+ // deadlock where server generation of CSS/JS requires an XHR signal.
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::Unblocked);
+ }
+
+ // Disable Necko-internal response timeouts.
+ nsCOMPtr<nsIHttpChannelInternal>
+ internalHttpChannel(do_QueryInterface(mChannel));
+ if (internalHttpChannel) {
+ internalHttpChannel->SetResponseTimeoutEnabled(false);
+ }
+
+ if (!mIsAnon) {
+ AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS);
+ }
+
+ // Bypass the network cache in cases where it makes no sense:
+ // POST responses are always unique, and we provide no API that would
+ // allow our consumers to specify a "cache key" to access old POST
+ // responses, so they are not worth caching.
+ if (mRequestMethod.EqualsLiteral("POST")) {
+ AddLoadFlags(mChannel,
+ nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE |
+ nsIRequest::INHIBIT_CACHING);
+ } else {
+ // When we are sync loading, we need to bypass the local cache when it would
+ // otherwise block us waiting for exclusive access to the cache. If we don't
+ // do this, then we could dead lock in some cases (see bug 309424).
+ //
+ // Also don't block on the cache entry on async if it is busy - favoring parallelism
+ // over cache hit rate for xhr. This does not disable the cache everywhere -
+ // only in cases where more than one channel for the same URI is accessed
+ // simultanously.
+ AddLoadFlags(mChannel, nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
+ }
+
+ // Since we expect XML data, set the type hint accordingly
+ // if the channel doesn't know any content type.
+ // This means that we always try to parse local files as XML
+ // ignoring return value, as this is not critical
+ nsAutoCString contentType;
+ if (NS_FAILED(mChannel->GetContentType(contentType)) ||
+ contentType.IsEmpty() ||
+ contentType.Equals(UNKNOWN_CONTENT_TYPE)) {
+ mChannel->SetContentType(NS_LITERAL_CSTRING("application/xml"));
+ }
+
+ // Set up the preflight if needed
+ if (!IsSystemXHR()) {
+ nsTArray<nsCString> CORSUnsafeHeaders;
+ mAuthorRequestHeaders.GetCORSUnsafeHeaders(CORSUnsafeHeaders);
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ loadInfo->SetCorsPreflightInfo(CORSUnsafeHeaders,
+ mFlagHadUploadListenersOnSend);
+ }
+
+ // Hook us up to listen to redirects and the like. Only do this very late
+ // since this creates a cycle between the channel and us. This cycle has
+ // to be manually broken if anything below fails.
+ mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
+ mChannel->SetNotificationCallbacks(this);
+
+ if (internalHttpChannel) {
+ internalHttpChannel->SetBlockAuthPrompt(ShouldBlockAuthPrompt());
+ }
+
+ // Because of bug 682305, we can't let listener be the XHR object itself
+ // because JS wouldn't be able to use it. So create a listener around 'this'.
+ // Make sure to hold a strong reference so that we don't leak the wrapper.
+ nsCOMPtr<nsIStreamListener> listener = new net::nsStreamListenerWrapper(this);
+
+ // Start reading from the channel
+ rv = mChannel->AsyncOpen2(listener);
+ listener = nullptr;
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Drop our ref to the channel to avoid cycles. Also drop channel's
+ // ref to us to be extra safe.
+ mChannel->SetNotificationCallbacks(mNotificationCallbacks);
+ mChannel = nullptr;
+
+ mErrorLoad = true;
+
+ // Per spec, we throw on sync errors, but not async.
+ if (mFlagSynchronous) {
+ return NS_ERROR_DOM_NETWORK_ERR;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
+{
+ if (!aVariant) {
+ return SendInternal(nullptr);
+ }
+
+ uint16_t dataType;
+ nsresult rv = aVariant->GetDataType(&dataType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dataType == nsIDataType::VTYPE_INTERFACE ||
+ dataType == nsIDataType::VTYPE_INTERFACE_IS) {
+ nsCOMPtr<nsISupports> supports;
+ nsID *iid;
+ rv = aVariant->GetAsInterface(&iid, getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ free(iid);
+
+ // document?
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(supports);
+ if (doc) {
+ RequestBody<nsIDocument> body(doc);
+ return SendInternal(&body);
+ }
+
+ // nsISupportsString?
+ nsCOMPtr<nsISupportsString> wstr = do_QueryInterface(supports);
+ if (wstr) {
+ nsAutoString string;
+ wstr->GetData(string);
+ RequestBody<const nsAString> body(&string);
+ return SendInternal(&body);
+ }
+
+ // nsIInputStream?
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(supports);
+ if (stream) {
+ RequestBody<nsIInputStream> body(stream);
+ return SendInternal(&body);
+ }
+
+ // nsIXHRSendable?
+ nsCOMPtr<nsIXHRSendable> sendable = do_QueryInterface(supports);
+ if (sendable) {
+ RequestBody<nsIXHRSendable> body(sendable);
+ return SendInternal(&body);
+ }
+
+ // ArrayBuffer?
+ JS::RootingContext* rootingCx = RootingCx();
+ JS::Rooted<JS::Value> realVal(rootingCx);
+
+ nsresult rv = aVariant->GetAsJSVal(&realVal);
+ if (NS_SUCCEEDED(rv) && !realVal.isPrimitive()) {
+ JS::Rooted<JSObject*> obj(rootingCx, realVal.toObjectOrNull());
+ RootedTypedArray<ArrayBuffer> buf(rootingCx);
+ if (buf.Init(obj)) {
+ RequestBody<const ArrayBuffer> body(&buf);
+ return SendInternal(&body);
+ }
+ }
+ } else if (dataType == nsIDataType::VTYPE_VOID ||
+ dataType == nsIDataType::VTYPE_EMPTY) {
+ return SendInternal(nullptr);
+ }
+
+ char16_t* data = nullptr;
+ uint32_t len = 0;
+ rv = aVariant->GetAsWStringWithSize(&len, &data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString string;
+ string.Adopt(data, len);
+
+ RequestBody<const nsAString> body(&string);
+ return SendInternal(&body);
+}
+
+nsresult
+XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
+{
+ NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
+
+ // Step 1
+ if (mState != State::opened) {
+ return NS_ERROR_DOM_INVALID_STATE_XHR_MUST_BE_OPENED;
+ }
+
+ // Step 2
+ if (mFlagSend) {
+ return NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_SENDING;
+ }
+
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT;
+ }
+
+ // If open() failed to create the channel, then throw a network error
+ // as per spec. We really should create the channel here in send(), but
+ // we have internal code relying on the channel being created in open().
+ if (!mChannel) {
+ return NS_ERROR_DOM_NETWORK_ERR;
+ }
+
+ PopulateNetworkInterfaceId();
+
+ // XXX We should probably send a warning to the JS console
+ // if there are no event listeners set and we are doing
+ // an asynchronous call.
+
+ mUploadTransferred = 0;
+ mUploadTotal = 0;
+ // By default we don't have any upload, so mark upload complete.
+ mUploadComplete = true;
+ mErrorLoad = false;
+ mLoadTotal = -1;
+ nsCOMPtr<nsIInputStream> uploadStream;
+ nsAutoCString uploadContentType;
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
+ if (aBody && httpChannel &&
+ !mRequestMethod.EqualsLiteral("GET") &&
+ !mRequestMethod.EqualsLiteral("HEAD")) {
+
+ nsAutoCString charset;
+ nsAutoCString defaultContentType;
+ uint64_t size_u64;
+ rv = aBody->GetAsStream(getter_AddRefs(uploadStream),
+ &size_u64, defaultContentType, charset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make sure it fits within js MAX_SAFE_INTEGER
+ mUploadTotal =
+ net::InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1;
+
+ if (uploadStream) {
+ // If author set no Content-Type, use the default from GetAsStream().
+ mAuthorRequestHeaders.Get("content-type", uploadContentType);
+ if (uploadContentType.IsVoid()) {
+ uploadContentType = defaultContentType;
+
+ if (!charset.IsEmpty()) {
+ // If we are providing the default content type, then we also need to
+ // provide a charset declaration.
+ uploadContentType.Append(NS_LITERAL_CSTRING(";charset="));
+ uploadContentType.Append(charset);
+ }
+ }
+
+ // We don't want to set a charset for streams.
+ if (!charset.IsEmpty()) {
+ // Replace all case-insensitive matches of the charset in the
+ // content-type with the correct case.
+ RequestHeaders::CharsetIterator iter(uploadContentType);
+ const nsCaseInsensitiveCStringComparator cmp;
+ while (iter.Next()) {
+ if (!iter.Equals(charset, cmp)) {
+ iter.Replace(charset);
+ }
+ }
+ }
+
+ mUploadComplete = false;
+ }
+ }
+
+ ResetResponse();
+
+ // Check if we should enable cross-origin upload listeners.
+ if (mUpload && mUpload->HasListeners()) {
+ mFlagHadUploadListenersOnSend = true;
+ }
+
+ mIsMappedArrayBuffer = false;
+ if (mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
+ Preferences::GetBool("dom.mapped_arraybuffer.enabled", true)) {
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString scheme;
+
+ rv = mChannel->GetURI(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv)) {
+ uri->GetScheme(scheme);
+ if (scheme.LowerCaseEqualsLiteral("jar")) {
+ mIsMappedArrayBuffer = true;
+ }
+ }
+ }
+
+ rv = InitiateFetch(uploadStream, mUploadTotal, uploadContentType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Start our timeout
+ mRequestSentTime = PR_Now();
+ StartTimeoutTimer();
+
+ mWaitingForOnStopRequest = true;
+
+ // Step 8
+ mFlagSend = true;
+
+ // If we're synchronous, spin an event loop here and wait
+ if (mFlagSynchronous) {
+ mFlagSyncLooping = true;
+
+ nsCOMPtr<nsIDocument> suspendedDoc;
+ nsCOMPtr<nsIRunnable> resumeTimeoutRunnable;
+ if (GetOwner()) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetOwner()->GetOuterWindow()->GetTop()) {
+ if (nsCOMPtr<nsPIDOMWindowInner> topInner = topWindow->GetCurrentInnerWindow()) {
+ suspendedDoc = topWindow->GetExtantDoc();
+ if (suspendedDoc) {
+ suspendedDoc->SuppressEventHandling(nsIDocument::eEvents);
+ }
+ topInner->Suspend();
+ resumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
+ }
+ }
+ }
+
+ StopProgressEventTimer();
+
+ SyncTimeoutType syncTimeoutType = MaybeStartSyncTimeoutTimer();
+ if (syncTimeoutType == eErrorOrExpired) {
+ Abort();
+ rv = NS_ERROR_DOM_NETWORK_ERR;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoSyncOperation sync(suspendedDoc);
+ nsIThread *thread = NS_GetCurrentThread();
+ while (mFlagSyncLooping) {
+ if (!NS_ProcessNextEvent(thread)) {
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ }
+
+ // Time expired... We should throw.
+ if (syncTimeoutType == eTimerStarted && !mSyncTimeoutTimer) {
+ rv = NS_ERROR_DOM_NETWORK_ERR;
+ }
+
+ CancelSyncTimeoutTimer();
+ }
+
+ if (suspendedDoc) {
+ suspendedDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents,
+ true);
+ }
+
+ if (resumeTimeoutRunnable) {
+ NS_DispatchToCurrentThread(resumeTimeoutRunnable);
+ }
+ } else {
+ // Now that we've successfully opened the channel, we can change state. Note
+ // that this needs to come after the AsyncOpen() and rv check, because this
+ // can run script that would try to restart this request, and that could end
+ // up doing our AsyncOpen on a null channel if the reentered AsyncOpen fails.
+ StopProgressEventTimer();
+
+ // Upload phase beginning; start the progress event timer if necessary.
+ if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) {
+ StartProgressEventTimer();
+ }
+ // Dispatch loadstart events
+ DispatchProgressEvent(this, ProgressEventType::loadstart, 0, -1);
+ if (mUpload && !mUploadComplete) {
+ DispatchProgressEvent(mUpload, ProgressEventType::loadstart,
+ 0, mUploadTotal);
+ }
+ }
+
+ if (!mChannel) {
+ // Per spec, silently fail on async request failures; throw for sync.
+ if (mFlagSynchronous) {
+ return NS_ERROR_DOM_NETWORK_ERR;
+ } else {
+ // Defer the actual sending of async events just in case listeners
+ // are attached after the send() method is called.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod<ProgressEventType>(this,
+ &XMLHttpRequestMainThread::CloseRequestWithError,
+ ProgressEventType::error));
+ return NS_OK;
+ }
+ }
+
+ return rv;
+}
+
+// http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader
+NS_IMETHODIMP
+XMLHttpRequestMainThread::SetRequestHeader(const nsACString& aName,
+ const nsACString& aValue)
+{
+ // Step 1
+ if (mState != State::opened) {
+ return NS_ERROR_DOM_INVALID_STATE_XHR_MUST_BE_OPENED;
+ }
+
+ // Step 2
+ if (mFlagSend) {
+ return NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_SENDING;
+ }
+
+ // Step 3
+ nsAutoCString value(aValue);
+ static const char kHTTPWhitespace[] = "\n\t\r ";
+ value.Trim(kHTTPWhitespace);
+
+ // Step 4
+ if (!NS_IsValidHTTPToken(aName) || !NS_IsReasonableHTTPHeaderValue(value)) {
+ return NS_ERROR_DOM_INVALID_HEADER_NAME;
+ }
+
+ // Step 5
+ bool isPrivilegedCaller = IsSystemXHR();
+ bool isForbiddenHeader = nsContentUtils::IsForbiddenRequestHeader(aName);
+ if (!isPrivilegedCaller && isForbiddenHeader) {
+ NS_ConvertUTF8toUTF16 name(aName);
+ const char16_t* params[] = { name.get() };
+ LogMessage("ForbiddenHeaderWarning", GetOwner(), params, ArrayLength(params));
+ return NS_OK;
+ }
+
+ // Step 6.1
+ // Skipping for now, as normalizing the case of header names may not be
+ // web-compatible. See bug 1285036.
+
+ // Step 6.2-6.3
+ // Gecko-specific: invalid headers can be set by privileged
+ // callers, but will not merge.
+ if (isPrivilegedCaller && isForbiddenHeader) {
+ mAuthorRequestHeaders.Set(aName, value);
+ } else {
+ mAuthorRequestHeaders.MergeOrSet(aName, value);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetTimeout(uint32_t *aTimeout)
+{
+ *aTimeout = Timeout();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::SetTimeout(uint32_t aTimeout)
+{
+ ErrorResult rv;
+ SetTimeout(aTimeout, rv);
+ return rv.StealNSResult();
+}
+
+void
+XMLHttpRequestMainThread::SetTimeout(uint32_t aTimeout, ErrorResult& aRv)
+{
+ if (mFlagSynchronous && mState != State::unsent && HasOrHasHadOwner()) {
+ /* Timeout is not supported for synchronous requests with an owning window,
+ per XHR2 spec. */
+ LogMessage("TimeoutSyncXHRWarning", GetOwner());
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC);
+ return;
+ }
+
+ mTimeoutMilliseconds = aTimeout;
+ if (mRequestSentTime) {
+ StartTimeoutTimer();
+ }
+}
+
+void
+XMLHttpRequestMainThread::StartTimeoutTimer()
+{
+ MOZ_ASSERT(mRequestSentTime,
+ "StartTimeoutTimer mustn't be called before the request was sent!");
+ if (mState == State::done) {
+ // do nothing!
+ return;
+ }
+
+ if (mTimeoutTimer) {
+ mTimeoutTimer->Cancel();
+ }
+
+ if (!mTimeoutMilliseconds) {
+ return;
+ }
+
+ if (!mTimeoutTimer) {
+ mTimeoutTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+ uint32_t elapsed =
+ (uint32_t)((PR_Now() - mRequestSentTime) / PR_USEC_PER_MSEC);
+ mTimeoutTimer->InitWithCallback(
+ this,
+ mTimeoutMilliseconds > elapsed ? mTimeoutMilliseconds - elapsed : 0,
+ nsITimer::TYPE_ONE_SHOT
+ );
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetReadyState(uint16_t *aState)
+{
+ *aState = ReadyState();
+ return NS_OK;
+}
+
+uint16_t
+XMLHttpRequestMainThread::ReadyState() const
+{
+ // Translate some of our internal states for external consumers
+ switch(mState) {
+ case State::unsent:
+ return UNSENT;
+ case State::opened:
+ return OPENED;
+ case State::headers_received:
+ return HEADERS_RECEIVED;
+ case State::loading:
+ return LOADING;
+ case State::done:
+ return DONE;
+ default:
+ MOZ_CRASH("Unknown state");
+ }
+ return 0;
+}
+
+void XMLHttpRequestMainThread::OverrideMimeType(const nsAString& aMimeType, ErrorResult& aRv)
+{
+ if (mState == State::loading || mState == State::done) {
+ ResetResponse();
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_LOADING_OR_DONE);
+ return;
+ }
+
+ mOverrideMimeType = aMimeType;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::SlowOverrideMimeType(const nsAString& aMimeType)
+{
+ ErrorResult aRv;
+ OverrideMimeType(aMimeType, aRv);
+ return aRv.StealNSResult();
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetMozBackgroundRequest(bool *_retval)
+{
+ *_retval = MozBackgroundRequest();
+ return NS_OK;
+}
+
+bool
+XMLHttpRequestMainThread::MozBackgroundRequest() const
+{
+ return mFlagBackgroundRequest;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::SetMozBackgroundRequest(bool aMozBackgroundRequest)
+{
+ if (!IsSystemXHR()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ if (mState != State::unsent) {
+ // Can't change this while we're in the middle of something.
+ return NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_SENDING;
+ }
+
+ mFlagBackgroundRequest = aMozBackgroundRequest;
+
+ return NS_OK;
+}
+
+void
+XMLHttpRequestMainThread::SetMozBackgroundRequest(bool aMozBackgroundRequest,
+ ErrorResult& aRv)
+{
+ // No errors for this webIDL method on main-thread.
+ SetMozBackgroundRequest(aMozBackgroundRequest);
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetWithCredentials(bool *_retval)
+{
+ *_retval = WithCredentials();
+ return NS_OK;
+}
+
+bool
+XMLHttpRequestMainThread::WithCredentials() const
+{
+ return mFlagACwithCredentials;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::SetWithCredentials(bool aWithCredentials)
+{
+ ErrorResult rv;
+ SetWithCredentials(aWithCredentials, rv);
+ return rv.StealNSResult();
+}
+
+void
+XMLHttpRequestMainThread::SetWithCredentials(bool aWithCredentials, ErrorResult& aRv)
+{
+ // Return error if we're already processing a request. Note that we can't use
+ // ReadyState() here, because it can't differentiate between "opened" and
+ // "sent", so we use mState directly.
+
+ if ((mState != State::unsent && mState != State::opened) ||
+ mFlagSend || mIsAnon) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_SENDING);
+ return;
+ }
+
+ mFlagACwithCredentials = aWithCredentials;
+}
+
+nsresult
+XMLHttpRequestMainThread::ChangeState(State aState, bool aBroadcast)
+{
+ mState = aState;
+ nsresult rv = NS_OK;
+
+ if (aState != State::headers_received && aState != State::loading) {
+ StopProgressEventTimer();
+ }
+
+
+ if (aBroadcast && (!mFlagSynchronous ||
+ aState == State::opened ||
+ aState == State::done)) {
+ rv = FireReadystatechangeEvent();
+ }
+
+ return rv;
+}
+
+/////////////////////////////////////////////////////
+// nsIChannelEventSink methods:
+//
+NS_IMETHODIMP
+XMLHttpRequestMainThread::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback *callback)
+{
+ NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
+
+ // Prepare to receive callback
+ mRedirectCallback = callback;
+ mNewRedirectChannel = aNewChannel;
+
+ if (mChannelEventSink) {
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> fwd =
+ EnsureXPCOMifier();
+
+ nsresult rv = mChannelEventSink->AsyncOnChannelRedirect(aOldChannel,
+ aNewChannel,
+ aFlags, fwd);
+ if (NS_FAILED(rv)) {
+ mRedirectCallback = nullptr;
+ mNewRedirectChannel = nullptr;
+ }
+ return rv;
+ }
+ OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+nsresult
+XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result)
+{
+ NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
+ NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
+
+ if (NS_SUCCEEDED(result)) {
+ mChannel = mNewRedirectChannel;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
+ if (httpChannel) {
+ // Ensure all original headers are duplicated for the new channel (bug #553888)
+ mAuthorRequestHeaders.ApplyToChannel(httpChannel);
+ }
+ } else {
+ mErrorLoad = true;
+ }
+
+ mNewRedirectChannel = nullptr;
+
+ mRedirectCallback->OnRedirectVerifyCallback(result);
+ mRedirectCallback = nullptr;
+
+ // It's important that we return success here. If we return the result code
+ // that we were passed, JavaScript callers who cancel the redirect will wind
+ // up throwing an exception in the process.
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////
+// nsIProgressEventSink methods:
+//
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::OnProgress(nsIRequest *aRequest, nsISupports *aContext, int64_t aProgress, int64_t aProgressMax)
+{
+ // When uploading, OnProgress reports also headers in aProgress and aProgressMax.
+ // So, try to remove the headers, if possible.
+ bool lengthComputable = (aProgressMax != -1);
+ if (InUploadPhase()) {
+ int64_t loaded = aProgress;
+ if (lengthComputable) {
+ int64_t headerSize = aProgressMax - mUploadTotal;
+ loaded -= headerSize;
+ }
+ mUploadTransferred = loaded;
+ mProgressSinceLastProgressEvent = true;
+
+ if (!mFlagSynchronous && !mProgressTimerIsActive) {
+ StartProgressEventTimer();
+ }
+ } else {
+ mLoadTotal = aProgressMax;
+ mLoadTransferred = aProgress;
+ // OnDataAvailable() handles mProgressSinceLastProgressEvent
+ // for the download phase.
+ }
+
+ if (mProgressEventSink) {
+ mProgressEventSink->OnProgress(aRequest, aContext, aProgress,
+ aProgressMax);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::OnStatus(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatus, const char16_t *aStatusArg)
+{
+ if (mProgressEventSink) {
+ mProgressEventSink->OnStatus(aRequest, aContext, aStatus, aStatusArg);
+ }
+
+ return NS_OK;
+}
+
+bool
+XMLHttpRequestMainThread::AllowUploadProgress()
+{
+ return !IsCrossSiteCORSRequest() ||
+ mFlagHadUploadListenersOnSend;
+}
+
+/////////////////////////////////////////////////////
+// nsIInterfaceRequestor methods:
+//
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetInterface(const nsIID & aIID, void **aResult)
+{
+ nsresult rv;
+
+ // Make sure to return ourselves for the channel event sink interface and
+ // progress event sink interface, no matter what. We can forward these to
+ // mNotificationCallbacks if it wants to get notifications for them. But we
+ // need to see these notifications for proper functioning.
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ mChannelEventSink = do_GetInterface(mNotificationCallbacks);
+ *aResult = static_cast<nsIChannelEventSink*>(EnsureXPCOMifier().take());
+ return NS_OK;
+ } else if (aIID.Equals(NS_GET_IID(nsIProgressEventSink))) {
+ mProgressEventSink = do_GetInterface(mNotificationCallbacks);
+ *aResult = static_cast<nsIProgressEventSink*>(EnsureXPCOMifier().take());
+ return NS_OK;
+ }
+
+ // Now give mNotificationCallbacks (if non-null) a chance to return the
+ // desired interface.
+ if (mNotificationCallbacks) {
+ rv = mNotificationCallbacks->GetInterface(aIID, aResult);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!");
+ return rv;
+ }
+ }
+
+ if (mFlagBackgroundRequest) {
+ nsCOMPtr<nsIInterfaceRequestor> badCertHandler(do_CreateInstance(NS_BADCERTHANDLER_CONTRACTID, &rv));
+
+ // Ignore failure to get component, we may not have all its dependencies
+ // available
+ if (NS_SUCCEEDED(rv)) {
+ rv = badCertHandler->GetInterface(aIID, aResult);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+ }
+ else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the an auth prompter for our window so that the parenting
+ // of the dialogs works as it should when using tabs.
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ if (GetOwner()) {
+ window = GetOwner()->GetOuterWindow();
+ }
+
+ return wwatch->GetPrompt(window, aIID,
+ reinterpret_cast<void**>(aResult));
+ }
+ // Now check for the various XHR non-DOM interfaces, except
+ // nsIProgressEventSink and nsIChannelEventSink which we already
+ // handled above.
+ else if (aIID.Equals(NS_GET_IID(nsIStreamListener))) {
+ *aResult = static_cast<nsIStreamListener*>(EnsureXPCOMifier().take());
+ return NS_OK;
+ }
+ else if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) {
+ *aResult = static_cast<nsIRequestObserver*>(EnsureXPCOMifier().take());
+ return NS_OK;
+ }
+ else if (aIID.Equals(NS_GET_IID(nsITimerCallback))) {
+ *aResult = static_cast<nsITimerCallback*>(EnsureXPCOMifier().take());
+ return NS_OK;
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+void
+XMLHttpRequestMainThread::GetInterface(JSContext* aCx, nsIJSID* aIID,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv)
+{
+ dom::GetInterface(aCx, this, aIID, aRetval, aRv);
+}
+
+XMLHttpRequestUpload*
+XMLHttpRequestMainThread::GetUpload(ErrorResult& aRv)
+{
+ if (!mUpload) {
+ mUpload = new XMLHttpRequestUpload(this);
+ }
+ return mUpload;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetUpload(nsIXMLHttpRequestUpload** aUpload)
+{
+ ErrorResult rv;
+ RefPtr<XMLHttpRequestUpload> upload = GetUpload(rv);
+ upload.forget(aUpload);
+ return rv.StealNSResult();
+}
+
+bool
+XMLHttpRequestMainThread::MozAnon() const
+{
+ return mIsAnon;
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetMozAnon(bool* aAnon)
+{
+ *aAnon = MozAnon();
+ return NS_OK;
+}
+
+bool
+XMLHttpRequestMainThread::MozSystem() const
+{
+ return IsSystemXHR();
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::GetMozSystem(bool* aSystem)
+{
+ *aSystem = MozSystem();
+ return NS_OK;
+}
+
+void
+XMLHttpRequestMainThread::HandleTimeoutCallback()
+{
+ if (mState == State::done) {
+ NS_NOTREACHED("XMLHttpRequestMainThread::HandleTimeoutCallback with completed request");
+ // do nothing!
+ return;
+ }
+
+ mFlagTimedOut = true;
+ CloseRequestWithError(ProgressEventType::timeout);
+}
+
+NS_IMETHODIMP
+XMLHttpRequestMainThread::Notify(nsITimer* aTimer)
+{
+ if (mProgressNotifier == aTimer) {
+ HandleProgressTimerCallback();
+ return NS_OK;
+ }
+
+ if (mTimeoutTimer == aTimer) {
+ HandleTimeoutCallback();
+ return NS_OK;
+ }
+
+ if (mSyncTimeoutTimer == aTimer) {
+ HandleSyncTimeoutTimer();
+ return NS_OK;
+ }
+
+ // Just in case some JS user wants to QI to nsITimerCallback and play with us...
+ NS_WARNING("Unexpected timer!");
+ return NS_ERROR_INVALID_POINTER;
+}
+
+void
+XMLHttpRequestMainThread::HandleProgressTimerCallback()
+{
+ // Don't fire the progress event if mLoadTotal is 0, see XHR spec step 6.1
+ if (!mLoadTotal && mLoadTransferred) {
+ return;
+ }
+
+ mProgressTimerIsActive = false;
+
+ if (!mProgressSinceLastProgressEvent || mErrorLoad) {
+ return;
+ }
+
+ if (InUploadPhase()) {
+ if (mUpload && !mUploadComplete) {
+ DispatchProgressEvent(mUpload, ProgressEventType::progress,
+ mUploadTransferred, mUploadTotal);
+ }
+ } else {
+ FireReadystatechangeEvent();
+ DispatchProgressEvent(this, ProgressEventType::progress,
+ mLoadTransferred, mLoadTotal);
+ }
+
+ mProgressSinceLastProgressEvent = false;
+
+ StartProgressEventTimer();
+}
+
+void
+XMLHttpRequestMainThread::StopProgressEventTimer()
+{
+ if (mProgressNotifier) {
+ mProgressTimerIsActive = false;
+ mProgressNotifier->Cancel();
+ }
+}
+
+void
+XMLHttpRequestMainThread::StartProgressEventTimer()
+{
+ if (!mProgressNotifier) {
+ mProgressNotifier = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+ if (mProgressNotifier) {
+ mProgressTimerIsActive = true;
+ mProgressNotifier->Cancel();
+ mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+XMLHttpRequestMainThread::SyncTimeoutType
+XMLHttpRequestMainThread::MaybeStartSyncTimeoutTimer()
+{
+ MOZ_ASSERT(mFlagSynchronous);
+
+ nsIDocument* doc = GetDocumentIfCurrent();
+ if (!doc || !doc->GetPageUnloadingEventTimeStamp()) {
+ return eNoTimerNeeded;
+ }
+
+ // If we are in a beforeunload or a unload event, we must force a timeout.
+ TimeDuration diff = (TimeStamp::NowLoRes() - doc->GetPageUnloadingEventTimeStamp());
+ if (diff.ToMilliseconds() > MAX_SYNC_TIMEOUT_WHEN_UNLOADING) {
+ return eErrorOrExpired;
+ }
+
+ mSyncTimeoutTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (!mSyncTimeoutTimer) {
+ return eErrorOrExpired;
+ }
+
+ uint32_t timeout = MAX_SYNC_TIMEOUT_WHEN_UNLOADING - diff.ToMilliseconds();
+ nsresult rv = mSyncTimeoutTimer->InitWithCallback(this, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ return NS_FAILED(rv) ? eErrorOrExpired : eTimerStarted;
+}
+
+void
+XMLHttpRequestMainThread::HandleSyncTimeoutTimer()
+{
+ MOZ_ASSERT(mSyncTimeoutTimer);
+ MOZ_ASSERT(mFlagSyncLooping);
+
+ CancelSyncTimeoutTimer();
+ Abort();
+}
+
+void
+XMLHttpRequestMainThread::CancelSyncTimeoutTimer()
+{
+ if (mSyncTimeoutTimer) {
+ mSyncTimeoutTimer->Cancel();
+ mSyncTimeoutTimer = nullptr;
+ }
+}
+
+already_AddRefed<nsXMLHttpRequestXPCOMifier>
+XMLHttpRequestMainThread::EnsureXPCOMifier()
+{
+ if (!mXPCOMifier) {
+ mXPCOMifier = new nsXMLHttpRequestXPCOMifier(this);
+ }
+ RefPtr<nsXMLHttpRequestXPCOMifier> newRef(mXPCOMifier);
+ return newRef.forget();
+}
+
+bool
+XMLHttpRequestMainThread::ShouldBlockAuthPrompt()
+{
+ // Verify that it's ok to prompt for credentials here, per spec
+ // http://xhr.spec.whatwg.org/#the-send%28%29-method
+
+ if (mAuthorRequestHeaders.Has("authorization")) {
+ return true;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ // Also skip if a username and/or password is provided in the URI.
+ nsCString username;
+ rv = uri->GetUsername(username);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsCString password;
+ rv = uri->GetPassword(password);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ if (!username.IsEmpty() || !password.IsEmpty()) {
+ return true;
+ }
+
+ return false;
+}
+
+void
+XMLHttpRequestMainThread::TruncateResponseText()
+{
+ mResponseText.Truncate();
+ XMLHttpRequestBinding::ClearCachedResponseTextValue(this);
+}
+
+NS_IMPL_ISUPPORTS(XMLHttpRequestMainThread::nsHeaderVisitor, nsIHttpHeaderVisitor)
+
+NS_IMETHODIMP XMLHttpRequestMainThread::
+nsHeaderVisitor::VisitHeader(const nsACString &header, const nsACString &value)
+{
+ if (mXHR.IsSafeHeader(header, mHttpChannel)) {
+ mHeaders.Append(header);
+ mHeaders.AppendLiteral(": ");
+ mHeaders.Append(value);
+ mHeaders.AppendLiteral("\r\n");
+ }
+ return NS_OK;
+}
+
+void
+XMLHttpRequestMainThread::MaybeCreateBlobStorage()
+{
+ MOZ_ASSERT(mResponseType == XMLHttpRequestResponseType::Blob);
+
+ if (mBlobStorage) {
+ return;
+ }
+
+ MutableBlobStorage::MutableBlobStorageType storageType =
+ BasePrincipal::Cast(mPrincipal)->PrivateBrowsingId() == 0
+ ? MutableBlobStorage::eCouldBeInTemporaryFile
+ : MutableBlobStorage::eOnlyInMemory;
+
+ mBlobStorage = new MutableBlobStorage(storageType);
+}
+
+void
+XMLHttpRequestMainThread::BlobStoreCompleted(MutableBlobStorage* aBlobStorage,
+ Blob* aBlob, nsresult aRv)
+{
+ // Ok, the state is changed...
+ if (mBlobStorage != aBlobStorage || NS_FAILED(aRv)) {
+ return;
+ }
+
+ MOZ_ASSERT(mState != State::done);
+
+ mResponseBlob = aBlob;
+ mBlobStorage = nullptr;
+
+ ChangeStateToDone();
+}
+
+// nsXMLHttpRequestXPCOMifier implementation
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpRequestXPCOMifier)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpRequestXPCOMifier)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpRequestXPCOMifier)
+
+// Can't NS_IMPL_CYCLE_COLLECTION( because mXHR has ambiguous
+// inheritance from nsISupports.
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequestXPCOMifier)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXMLHttpRequestXPCOMifier)
+if (tmp->mXHR) {
+ tmp->mXHR->mXPCOMifier = nullptr;
+}
+NS_IMPL_CYCLE_COLLECTION_UNLINK(mXHR)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXMLHttpRequestXPCOMifier)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXHR)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMETHODIMP
+nsXMLHttpRequestXPCOMifier::GetInterface(const nsIID & aIID, void **aResult)
+{
+ // Return ourselves for the things we implement (except
+ // nsIInterfaceRequestor) and the XHR for the rest.
+ if (!aIID.Equals(NS_GET_IID(nsIInterfaceRequestor))) {
+ nsresult rv = QueryInterface(aIID, aResult);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+ }
+
+ return mXHR->GetInterface(aIID, aResult);
+}
+
+ArrayBufferBuilder::ArrayBufferBuilder()
+ : mDataPtr(nullptr),
+ mCapacity(0),
+ mLength(0),
+ mMapPtr(nullptr)
+{
+}
+
+ArrayBufferBuilder::~ArrayBufferBuilder()
+{
+ reset();
+}
+
+void
+ArrayBufferBuilder::reset()
+{
+ if (mDataPtr) {
+ JS_free(nullptr, mDataPtr);
+ }
+
+ if (mMapPtr) {
+ JS_ReleaseMappedArrayBufferContents(mMapPtr, mLength);
+ mMapPtr = nullptr;
+ }
+
+ mDataPtr = nullptr;
+ mCapacity = mLength = 0;
+}
+
+bool
+ArrayBufferBuilder::setCapacity(uint32_t aNewCap)
+{
+ MOZ_ASSERT(!mMapPtr);
+
+ // To ensure that realloc won't free mDataPtr, use a size of 1
+ // instead of 0.
+ uint8_t* newdata = (uint8_t *) js_realloc(mDataPtr, aNewCap ? aNewCap : 1);
+
+ if (!newdata) {
+ return false;
+ }
+
+ if (aNewCap > mCapacity) {
+ memset(newdata + mCapacity, 0, aNewCap - mCapacity);
+ }
+
+ mDataPtr = newdata;
+ mCapacity = aNewCap;
+ if (mLength > aNewCap) {
+ mLength = aNewCap;
+ }
+
+ return true;
+}
+
+bool
+ArrayBufferBuilder::append(const uint8_t *aNewData, uint32_t aDataLen,
+ uint32_t aMaxGrowth)
+{
+ MOZ_ASSERT(!mMapPtr);
+
+ CheckedUint32 neededCapacity = mLength;
+ neededCapacity += aDataLen;
+ if (!neededCapacity.isValid()) {
+ return false;
+ }
+ if (mLength + aDataLen > mCapacity) {
+ CheckedUint32 newcap = mCapacity;
+ // Double while under aMaxGrowth or if not specified.
+ if (!aMaxGrowth || mCapacity < aMaxGrowth) {
+ newcap *= 2;
+ } else {
+ newcap += aMaxGrowth;
+ }
+
+ if (!newcap.isValid()) {
+ return false;
+ }
+
+ // But make sure there's always enough to satisfy our request.
+ if (newcap.value() < neededCapacity.value()) {
+ newcap = neededCapacity;
+ }
+
+ if (!setCapacity(newcap.value())) {
+ return false;
+ }
+ }
+
+ // Assert that the region isn't overlapping so we can memcpy.
+ MOZ_ASSERT(!areOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength,
+ aDataLen));
+
+ memcpy(mDataPtr + mLength, aNewData, aDataLen);
+ mLength += aDataLen;
+
+ return true;
+}
+
+JSObject*
+ArrayBufferBuilder::getArrayBuffer(JSContext* aCx)
+{
+ if (mMapPtr) {
+ JSObject* obj = JS_NewMappedArrayBufferWithContents(aCx, mLength, mMapPtr);
+ if (!obj) {
+ JS_ReleaseMappedArrayBufferContents(mMapPtr, mLength);
+ }
+ mMapPtr = nullptr;
+
+ // The memory-mapped contents will be released when the ArrayBuffer becomes
+ // detached or is GC'd.
+ return obj;
+ }
+
+ // we need to check for mLength == 0, because nothing may have been
+ // added
+ if (mCapacity > mLength || mLength == 0) {
+ if (!setCapacity(mLength)) {
+ return nullptr;
+ }
+ }
+
+ JSObject* obj = JS_NewArrayBufferWithContents(aCx, mLength, mDataPtr);
+ mLength = mCapacity = 0;
+ if (!obj) {
+ js_free(mDataPtr);
+ }
+ mDataPtr = nullptr;
+ return obj;
+}
+
+nsresult
+ArrayBufferBuilder::mapToFileInPackage(const nsCString& aFile,
+ nsIFile* aJarFile)
+{
+ nsresult rv;
+
+ // Open Jar file to get related attributes of target file.
+ RefPtr<nsZipArchive> zip = new nsZipArchive();
+ rv = zip->OpenArchive(aJarFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsZipItem* zipItem = zip->GetItem(aFile.get());
+ if (!zipItem) {
+ return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+ }
+
+ // If file was added to the package as stored(uncompressed), map to the
+ // offset of file in zip package.
+ if (!zipItem->Compression()) {
+ uint32_t offset = zip->GetDataOffset(zipItem);
+ uint32_t size = zipItem->RealSize();
+ mozilla::AutoFDClose pr_fd;
+ rv = aJarFile->OpenNSPRFileDesc(PR_RDONLY, 0, &pr_fd.rwget());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mMapPtr = JS_CreateMappedArrayBufferContents(PR_FileDesc2NativeHandle(pr_fd),
+ offset, size);
+ if (mMapPtr) {
+ mLength = size;
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+/* static */ bool
+ArrayBufferBuilder::areOverlappingRegions(const uint8_t* aStart1,
+ uint32_t aLength1,
+ const uint8_t* aStart2,
+ uint32_t aLength2)
+{
+ const uint8_t* end1 = aStart1 + aLength1;
+ const uint8_t* end2 = aStart2 + aLength2;
+
+ const uint8_t* max_start = aStart1 > aStart2 ? aStart1 : aStart2;
+ const uint8_t* min_end = end1 < end2 ? end1 : end2;
+
+ return max_start < min_end;
+}
+
+RequestHeaders::RequestHeader*
+RequestHeaders::Find(const nsACString& aName)
+{
+ const nsCaseInsensitiveCStringComparator ignoreCase;
+ for (RequestHeaders::RequestHeader& header : mHeaders) {
+ if (header.mName.Equals(aName, ignoreCase)) {
+ return &header;
+ }
+ }
+ return nullptr;
+}
+
+bool
+RequestHeaders::Has(const char* aName)
+{
+ return Has(nsDependentCString(aName));
+}
+
+bool
+RequestHeaders::Has(const nsACString& aName)
+{
+ return !!Find(aName);
+}
+
+void
+RequestHeaders::Get(const char* aName, nsACString& aValue)
+{
+ Get(nsDependentCString(aName), aValue);
+}
+
+void
+RequestHeaders::Get(const nsACString& aName, nsACString& aValue)
+{
+ RequestHeader* header = Find(aName);
+ if (header) {
+ aValue = header->mValue;
+ } else {
+ aValue.SetIsVoid(true);
+ }
+}
+
+void
+RequestHeaders::Set(const char* aName, const nsACString& aValue)
+{
+ Set(nsDependentCString(aName), aValue);
+}
+
+void
+RequestHeaders::Set(const nsACString& aName, const nsACString& aValue)
+{
+ RequestHeader* header = Find(aName);
+ if (header) {
+ header->mValue.Assign(aValue);
+ } else {
+ RequestHeader newHeader = {
+ nsCString(aName), nsCString(aValue)
+ };
+ mHeaders.AppendElement(newHeader);
+ }
+}
+
+void
+RequestHeaders::MergeOrSet(const char* aName, const nsACString& aValue)
+{
+ MergeOrSet(nsDependentCString(aName), aValue);
+}
+
+void
+RequestHeaders::MergeOrSet(const nsACString& aName, const nsACString& aValue)
+{
+ RequestHeader* header = Find(aName);
+ if (header) {
+ header->mValue.AppendLiteral(", ");
+ header->mValue.Append(aValue);
+ } else {
+ RequestHeader newHeader = {
+ nsCString(aName), nsCString(aValue)
+ };
+ mHeaders.AppendElement(newHeader);
+ }
+}
+
+void
+RequestHeaders::Clear()
+{
+ mHeaders.Clear();
+}
+
+void
+RequestHeaders::ApplyToChannel(nsIHttpChannel* aHttpChannel) const
+{
+ for (const RequestHeader& header : mHeaders) {
+ if (header.mValue.IsEmpty()) {
+ aHttpChannel->SetEmptyRequestHeader(header.mName);
+ } else {
+ aHttpChannel->SetRequestHeader(header.mName, header.mValue, false);
+ }
+ }
+}
+
+void
+RequestHeaders::GetCORSUnsafeHeaders(nsTArray<nsCString>& aArray) const
+{
+ static const char *kCrossOriginSafeHeaders[] = {
+ "accept", "accept-language", "content-language", "content-type",
+ "last-event-id"
+ };
+ const uint32_t kCrossOriginSafeHeadersLength =
+ ArrayLength(kCrossOriginSafeHeaders);
+ for (const RequestHeader& header : mHeaders) {
+ bool safe = false;
+ for (uint32_t i = 0; i < kCrossOriginSafeHeadersLength; ++i) {
+ if (header.mName.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
+ safe = true;
+ break;
+ }
+ }
+ if (!safe) {
+ aArray.AppendElement(header.mName);
+ }
+ }
+}
+
+RequestHeaders::CharsetIterator::CharsetIterator(nsACString& aSource) :
+ mValid(false),
+ mCurPos(-1),
+ mCurLen(-1),
+ mCutoff(aSource.Length()),
+ mSource(aSource)
+{
+}
+
+bool
+RequestHeaders::CharsetIterator::Equals(const nsACString& aOther,
+ const nsCStringComparator& aCmp) const
+{
+ if (mValid) {
+ return Substring(mSource, mCurPos, mCurLen).Equals(aOther, aCmp);
+ } else {
+ return false;
+ }
+}
+
+void
+RequestHeaders::CharsetIterator::Replace(const nsACString& aReplacement)
+{
+ if (mValid) {
+ mSource.Replace(mCurPos, mCurLen, aReplacement);
+ mCurLen = aReplacement.Length();
+ }
+}
+
+bool
+RequestHeaders::CharsetIterator::Next()
+{
+ int32_t start, end;
+ nsAutoCString charset;
+
+ // Look for another charset declaration in the string, limiting the
+ // search to only the characters before the parts we've already searched
+ // (before mCutoff), so that we don't find the same charset twice.
+ NS_ExtractCharsetFromContentType(Substring(mSource, 0, mCutoff),
+ charset, &mValid, &start, &end);
+
+ if (!mValid) {
+ return false;
+ }
+
+ // Everything after the = sign is the part of the charset we want.
+ mCurPos = mSource.FindChar('=', start) + 1;
+ mCurLen = end - mCurPos;
+
+ // Special case: the extracted charset is quoted with single quotes.
+ // For the purpose of preserving what was set we want to handle them
+ // as delimiters (although they aren't really).
+ if (charset.Length() >= 2 &&
+ charset.First() == '\'' &&
+ charset.Last() == '\'') {
+ ++mCurPos;
+ mCurLen -= 2;
+ }
+
+ mCutoff = start;
+
+ return true;
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/xhr/XMLHttpRequestMainThread.h b/dom/xhr/XMLHttpRequestMainThread.h
new file mode 100644
index 000000000..c9bcddf99
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -0,0 +1,872 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_XMLHttpRequestMainThread_h
+#define mozilla_dom_XMLHttpRequestMainThread_h
+
+#include <bitset>
+#include "nsAutoPtr.h"
+#include "nsIXMLHttpRequest.h"
+#include "nsISupportsUtils.h"
+#include "nsIURI.h"
+#include "nsIHttpChannel.h"
+#include "nsIDocument.h"
+#include "nsIStreamListener.h"
+#include "nsWeakReference.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIProgressEventSink.h"
+#include "nsJSUtils.h"
+#include "nsTArray.h"
+#include "nsITimer.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsISizeOfEventTarget.h"
+#include "nsIXPConnect.h"
+#include "nsIInputStream.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/dom/MutableBlobStorage.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/XMLHttpRequest.h"
+#include "mozilla/dom/XMLHttpRequestBinding.h"
+#include "mozilla/dom/XMLHttpRequestEventTarget.h"
+#include "mozilla/dom/XMLHttpRequestString.h"
+
+#ifdef Status
+/* Xlib headers insist on this for some reason... Nuke it because
+ it'll override our member name */
+#undef Status
+#endif
+
+class nsIJARChannel;
+class nsILoadGroup;
+class nsIUnicodeDecoder;
+class nsIJSID;
+
+namespace mozilla {
+namespace dom {
+
+class Blob;
+class BlobSet;
+class DOMString;
+class FormData;
+class URLSearchParams;
+class XMLHttpRequestUpload;
+struct OriginAttributesDictionary;
+
+// A helper for building up an ArrayBuffer object's data
+// before creating the ArrayBuffer itself. Will do doubling
+// based reallocation, up to an optional maximum growth given.
+//
+// When all the data has been appended, call getArrayBuffer,
+// passing in the JSContext* for which the ArrayBuffer object
+// is to be created. This also implicitly resets the builder,
+// or it can be reset explicitly at any point by calling reset().
+class ArrayBufferBuilder
+{
+ uint8_t* mDataPtr;
+ uint32_t mCapacity;
+ uint32_t mLength;
+ void* mMapPtr;
+public:
+ ArrayBufferBuilder();
+ ~ArrayBufferBuilder();
+
+ void reset();
+
+ // Will truncate if aNewCap is < length().
+ bool setCapacity(uint32_t aNewCap);
+
+ // Append aDataLen bytes from data to the current buffer. If we
+ // need to grow the buffer, grow by doubling the size up to a
+ // maximum of aMaxGrowth (if given). If aDataLen is greater than
+ // what the new capacity would end up as, then grow by aDataLen.
+ //
+ // The data parameter must not overlap with anything beyond the
+ // builder's current valid contents [0..length)
+ bool append(const uint8_t* aNewData, uint32_t aDataLen,
+ uint32_t aMaxGrowth = 0);
+
+ uint32_t length() { return mLength; }
+ uint32_t capacity() { return mCapacity; }
+
+ JSObject* getArrayBuffer(JSContext* aCx);
+
+ // Memory mapping to starting position of file(aFile) in the zip
+ // package(aJarFile).
+ //
+ // The file in the zip package has to be uncompressed and the starting
+ // position of the file must be aligned according to array buffer settings
+ // in JS engine.
+ nsresult mapToFileInPackage(const nsCString& aFile, nsIFile* aJarFile);
+
+protected:
+ static bool areOverlappingRegions(const uint8_t* aStart1, uint32_t aLength1,
+ const uint8_t* aStart2, uint32_t aLength2);
+};
+
+class nsXMLHttpRequestXPCOMifier;
+
+class RequestHeaders
+{
+ struct RequestHeader
+ {
+ nsCString mName;
+ nsCString mValue;
+ };
+ nsTArray<RequestHeader> mHeaders;
+ RequestHeader* Find(const nsACString& aName);
+
+public:
+ class CharsetIterator
+ {
+ bool mValid;
+ int32_t mCurPos, mCurLen, mCutoff;
+ nsACString& mSource;
+
+ public:
+ explicit CharsetIterator(nsACString& aSource);
+ bool Equals(const nsACString& aOther, const nsCStringComparator& aCmp) const;
+ void Replace(const nsACString& aReplacement);
+ bool Next();
+ };
+
+ bool Has(const char* aName);
+ bool Has(const nsACString& aName);
+ void Get(const char* aName, nsACString& aValue);
+ void Get(const nsACString& aName, nsACString& aValue);
+ void Set(const char* aName, const nsACString& aValue);
+ void Set(const nsACString& aName, const nsACString& aValue);
+ void MergeOrSet(const char* aName, const nsACString& aValue);
+ void MergeOrSet(const nsACString& aName, const nsACString& aValue);
+ void Clear();
+ void ApplyToChannel(nsIHttpChannel* aChannel) const;
+ void GetCORSUnsafeHeaders(nsTArray<nsCString>& aArray) const;
+};
+
+// Make sure that any non-DOM interfaces added here are also added to
+// nsXMLHttpRequestXPCOMifier.
+class XMLHttpRequestMainThread final : public XMLHttpRequest,
+ public nsIXMLHttpRequest,
+ public nsIJSXMLHttpRequest,
+ public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIProgressEventSink,
+ public nsIInterfaceRequestor,
+ public nsSupportsWeakReference,
+ public nsITimerCallback,
+ public nsISizeOfEventTarget,
+ public MutableBlobStorageCallback
+{
+ friend class nsXHRParseEndListener;
+ friend class nsXMLHttpRequestXPCOMifier;
+
+public:
+ enum class ProgressEventType : uint8_t {
+ loadstart,
+ progress,
+ error,
+ abort,
+ timeout,
+ load,
+ loadend,
+ ENUM_MAX
+ };
+
+ XMLHttpRequestMainThread();
+
+ void Construct(nsIPrincipal* aPrincipal,
+ nsIGlobalObject* aGlobalObject,
+ nsIURI* aBaseURI = nullptr,
+ nsILoadGroup* aLoadGroup = nullptr)
+ {
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT_IF(nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(
+ aGlobalObject), win->IsInnerWindow());
+ mPrincipal = aPrincipal;
+ BindToOwner(aGlobalObject);
+ mBaseURI = aBaseURI;
+ mLoadGroup = aLoadGroup;
+ }
+
+ void InitParameters(bool aAnon, bool aSystem);
+
+ void SetParameters(bool aAnon, bool aSystem)
+ {
+ mIsAnon = aAnon || aSystem;
+ mIsSystem = aSystem;
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIXMLHttpRequest
+ NS_DECL_NSIXMLHTTPREQUEST
+
+ NS_FORWARD_NSIXMLHTTPREQUESTEVENTTARGET(XMLHttpRequestEventTarget::)
+
+ // nsIStreamListener
+ NS_DECL_NSISTREAMLISTENER
+
+ // nsIRequestObserver
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // nsIChannelEventSink
+ NS_DECL_NSICHANNELEVENTSINK
+
+ // nsIProgressEventSink
+ NS_DECL_NSIPROGRESSEVENTSINK
+
+ // nsIInterfaceRequestor
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ // nsITimerCallback
+ NS_DECL_NSITIMERCALLBACK
+
+ // nsISizeOfEventTarget
+ virtual size_t
+ SizeOfEventTargetIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ NS_REALLY_FORWARD_NSIDOMEVENTTARGET(XMLHttpRequestEventTarget)
+
+ // states
+ virtual uint16_t ReadyState() const override;
+
+ // request
+ nsresult CreateChannel();
+ nsresult InitiateFetch(nsIInputStream* aUploadStream,
+ int64_t aUploadLength,
+ nsACString& aUploadContentType);
+
+ virtual void
+ Open(const nsACString& aMethod, const nsAString& aUrl,
+ ErrorResult& aRv) override;
+
+ virtual void
+ Open(const nsACString& aMethod, const nsAString& aUrl, bool aAsync,
+ const nsAString& aUsername, const nsAString& aPassword,
+ ErrorResult& aRv) override;
+
+ nsresult
+ Open(const nsACString& aMethod,
+ const nsACString& aUrl,
+ bool aAsync,
+ const nsAString& aUsername,
+ const nsAString& aPassword);
+
+ virtual void
+ SetRequestHeader(const nsACString& aName, const nsACString& aValue,
+ ErrorResult& aRv) override
+ {
+ aRv = SetRequestHeader(aName, aValue);
+ }
+
+ virtual uint32_t
+ Timeout() const override
+ {
+ return mTimeoutMilliseconds;
+ }
+
+ virtual void
+ SetTimeout(uint32_t aTimeout, ErrorResult& aRv) override;
+
+ virtual bool WithCredentials() const override;
+
+ virtual void
+ SetWithCredentials(bool aWithCredentials, ErrorResult& aRv) override;
+
+ virtual XMLHttpRequestUpload*
+ GetUpload(ErrorResult& aRv) override;
+
+private:
+ virtual ~XMLHttpRequestMainThread();
+
+ class RequestBodyBase
+ {
+ public:
+ virtual nsresult GetAsStream(nsIInputStream** aResult,
+ uint64_t* aContentLength,
+ nsACString& aContentType,
+ nsACString& aCharset) const
+ {
+ NS_ASSERTION(false, "RequestBodyBase should not be used directly.");
+ return NS_ERROR_FAILURE;
+ }
+ };
+
+ template<typename Type>
+ class RequestBody final : public RequestBodyBase
+ {
+ Type* mBody;
+ public:
+ explicit RequestBody(Type* aBody) : mBody(aBody)
+ {
+ }
+ nsresult GetAsStream(nsIInputStream** aResult,
+ uint64_t* aContentLength,
+ nsACString& aContentType,
+ nsACString& aCharset) const override;
+ };
+
+ nsresult SendInternal(const RequestBodyBase* aBody);
+
+ bool IsCrossSiteCORSRequest() const;
+ bool IsDeniedCrossSiteCORSRequest();
+
+ // Tell our channel what network interface ID we were told to use.
+ // If it's an HTTP channel and we were told to use a non-default
+ // interface ID.
+ void PopulateNetworkInterfaceId();
+
+public:
+ virtual void
+ Send(JSContext* /*aCx*/, ErrorResult& aRv) override
+ {
+ aRv = SendInternal(nullptr);
+ }
+
+ virtual void
+ Send(JSContext* /*aCx*/, const ArrayBuffer& aArrayBuffer,
+ ErrorResult& aRv) override
+ {
+ RequestBody<const ArrayBuffer> body(&aArrayBuffer);
+ aRv = SendInternal(&body);
+ }
+
+ virtual void
+ Send(JSContext* /*aCx*/, const ArrayBufferView& aArrayBufferView,
+ ErrorResult& aRv) override
+ {
+ RequestBody<const ArrayBufferView> body(&aArrayBufferView);
+ aRv = SendInternal(&body);
+ }
+
+ virtual void
+ Send(JSContext* /*aCx*/, Blob& aBlob, ErrorResult& aRv) override
+ {
+ RequestBody<Blob> body(&aBlob);
+ aRv = SendInternal(&body);
+ }
+
+ virtual void Send(JSContext* /*aCx*/, URLSearchParams& aURLSearchParams,
+ ErrorResult& aRv) override
+ {
+ RequestBody<URLSearchParams> body(&aURLSearchParams);
+ aRv = SendInternal(&body);
+ }
+
+ virtual void
+ Send(JSContext* /*aCx*/, nsIDocument& aDoc, ErrorResult& aRv) override
+ {
+ RequestBody<nsIDocument> body(&aDoc);
+ aRv = SendInternal(&body);
+ }
+
+ virtual void
+ Send(JSContext* aCx, const nsAString& aString, ErrorResult& aRv) override
+ {
+ if (DOMStringIsNull(aString)) {
+ Send(aCx, aRv);
+ } else {
+ RequestBody<const nsAString> body(&aString);
+ aRv = SendInternal(&body);
+ }
+ }
+
+ virtual void
+ Send(JSContext* /*aCx*/, FormData& aFormData, ErrorResult& aRv) override
+ {
+ RequestBody<FormData> body(&aFormData);
+ aRv = SendInternal(&body);
+ }
+
+ virtual void
+ Send(JSContext* aCx, nsIInputStream* aStream, ErrorResult& aRv) override
+ {
+ NS_ASSERTION(aStream, "Null should go to string version");
+ RequestBody<nsIInputStream> body(aStream);
+ aRv = SendInternal(&body);
+ }
+
+ void
+ Abort() {
+ ErrorResult rv;
+ Abort(rv);
+ MOZ_ASSERT(!rv.Failed());
+ }
+
+ virtual void
+ Abort(ErrorResult& aRv) override;
+
+ // response
+ virtual void
+ GetResponseURL(nsAString& aUrl) override;
+
+ virtual uint32_t
+ GetStatus(ErrorResult& aRv) override;
+
+ virtual void
+ GetStatusText(nsACString& aStatusText, ErrorResult& aRv) override;
+
+ virtual void
+ GetResponseHeader(const nsACString& aHeader, nsACString& aResult,
+ ErrorResult& aRv) override;
+
+ void
+ GetResponseHeader(const nsAString& aHeader, nsAString& aResult,
+ ErrorResult& aRv)
+ {
+ nsAutoCString result;
+ GetResponseHeader(NS_ConvertUTF16toUTF8(aHeader), result, aRv);
+ if (result.IsVoid()) {
+ aResult.SetIsVoid(true);
+ }
+ else {
+ // The result value should be inflated:
+ CopyASCIItoUTF16(result, aResult);
+ }
+ }
+
+ virtual void
+ GetAllResponseHeaders(nsACString& aResponseHeaders,
+ ErrorResult& aRv) override;
+
+ bool IsSafeHeader(const nsACString& aHeaderName,
+ NotNull<nsIHttpChannel*> aHttpChannel) const;
+
+ virtual void
+ OverrideMimeType(const nsAString& aMimeType, ErrorResult& aRv) override;
+
+ virtual XMLHttpRequestResponseType
+ ResponseType() const override
+ {
+ return XMLHttpRequestResponseType(mResponseType);
+ }
+
+ virtual void
+ SetResponseType(XMLHttpRequestResponseType aType,
+ ErrorResult& aRv) override;
+
+ virtual void
+ GetResponse(JSContext* aCx, JS::MutableHandle<JS::Value> aResponse,
+ ErrorResult& aRv) override;
+
+ virtual void
+ GetResponseText(DOMString& aResponseText, ErrorResult& aRv) override;
+
+ void
+ GetResponseText(XMLHttpRequestStringSnapshot& aSnapshot,
+ ErrorResult& aRv);
+
+ virtual nsIDocument*
+ GetResponseXML(ErrorResult& aRv) override;
+
+ virtual bool
+ MozBackgroundRequest() const override;
+
+ virtual void
+ SetMozBackgroundRequest(bool aMozBackgroundRequest, ErrorResult& aRv) override;
+
+ virtual bool
+ MozAnon() const override;
+
+ virtual bool
+ MozSystem() const override;
+
+ virtual nsIChannel*
+ GetChannel() const override
+ {
+ return mChannel;
+ }
+
+ virtual void
+ GetNetworkInterfaceId(nsACString& aId) const override
+ {
+ aId = mNetworkInterfaceId;
+ }
+
+ virtual void
+ SetNetworkInterfaceId(const nsACString& aId) override
+ {
+ mNetworkInterfaceId = aId;
+ }
+
+ // We need a GetInterface callable from JS for chrome JS
+ virtual void
+ GetInterface(JSContext* aCx, nsIJSID* aIID,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv) override;
+
+ // This fires a trusted readystatechange event, which is not cancelable and
+ // doesn't bubble.
+ nsresult FireReadystatechangeEvent();
+ void DispatchProgressEvent(DOMEventTargetHelper* aTarget,
+ const ProgressEventType aType,
+ int64_t aLoaded, int64_t aTotal);
+
+ // This is called by the factory constructor.
+ nsresult Init();
+
+ nsresult init(nsIPrincipal* principal,
+ nsPIDOMWindowInner* globalObject,
+ nsIURI* baseURI);
+
+ void SetRequestObserver(nsIRequestObserver* aObserver);
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED(XMLHttpRequestMainThread,
+ XMLHttpRequest)
+ bool AllowUploadProgress();
+
+ virtual void DisconnectFromOwner() override;
+
+ static void SetDontWarnAboutSyncXHR(bool aVal)
+ {
+ sDontWarnAboutSyncXHR = aVal;
+ }
+ static bool DontWarnAboutSyncXHR()
+ {
+ return sDontWarnAboutSyncXHR;
+ }
+
+ virtual void
+ SetOriginAttributes(const mozilla::dom::OriginAttributesDictionary& aAttrs) override;
+
+ void BlobStoreCompleted(MutableBlobStorage* aBlobStorage,
+ Blob* aBlob,
+ nsresult aResult) override;
+
+protected:
+ // XHR states are meant to mirror the XHR2 spec:
+ // https://xhr.spec.whatwg.org/#states
+ enum class State : uint8_t {
+ unsent, // object has been constructed.
+ opened, // open() has been successfully invoked.
+ headers_received, // redirects followed and response headers received.
+ loading, // response body is being received.
+ done, // data transfer concluded, whether success or error.
+ };
+
+ nsresult DetectCharset();
+ nsresult AppendToResponseText(const char * aBuffer, uint32_t aBufferLen);
+ static nsresult StreamReaderFunc(nsIInputStream* in,
+ void* closure,
+ const char* fromRawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount);
+ nsresult CreateResponseParsedJSON(JSContext* aCx);
+ void CreatePartialBlob(ErrorResult& aRv);
+ bool CreateDOMBlob(nsIRequest *request);
+ // Change the state of the object with this. The broadcast argument
+ // determines if the onreadystatechange listener should be called.
+ nsresult ChangeState(State aState, bool aBroadcast = true);
+ already_AddRefed<nsILoadGroup> GetLoadGroup() const;
+ nsIURI *GetBaseURI();
+
+ already_AddRefed<nsIHttpChannel> GetCurrentHttpChannel();
+ already_AddRefed<nsIJARChannel> GetCurrentJARChannel();
+
+ void TruncateResponseText();
+
+ bool IsSystemXHR() const;
+ bool InUploadPhase() const;
+
+ void OnBodyParseEnd();
+ void ChangeStateToDone();
+
+ void StartProgressEventTimer();
+ void StopProgressEventTimer();
+
+ void MaybeCreateBlobStorage();
+
+ nsresult OnRedirectVerifyCallback(nsresult result);
+
+ already_AddRefed<nsXMLHttpRequestXPCOMifier> EnsureXPCOMifier();
+
+ nsCOMPtr<nsISupports> mContext;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCString mRequestMethod;
+ nsCOMPtr<nsIURI> mRequestURL;
+ nsCOMPtr<nsIDocument> mResponseXML;
+
+ nsCOMPtr<nsIStreamListener> mXMLParserStreamListener;
+
+ // used to implement getAllResponseHeaders()
+ class nsHeaderVisitor : public nsIHttpHeaderVisitor
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPHEADERVISITOR
+ nsHeaderVisitor(const XMLHttpRequestMainThread& aXMLHttpRequest,
+ NotNull<nsIHttpChannel*> aHttpChannel)
+ : mXHR(aXMLHttpRequest), mHttpChannel(aHttpChannel) {}
+ const nsACString &Headers() { return mHeaders; }
+ private:
+ virtual ~nsHeaderVisitor() {}
+
+ nsCString mHeaders;
+ const XMLHttpRequestMainThread& mXHR;
+ NotNull<nsCOMPtr<nsIHttpChannel>> mHttpChannel;
+ };
+
+ // The bytes of our response body. Only used for DEFAULT, ARRAYBUFFER and
+ // BLOB responseTypes
+ nsCString mResponseBody;
+
+ // The text version of our response body. This is incrementally decoded into
+ // as we receive network data. However for the DEFAULT responseType we
+ // lazily decode into this from mResponseBody only when .responseText is
+ // accessed.
+ // Only used for DEFAULT and TEXT responseTypes.
+ XMLHttpRequestString mResponseText;
+
+ // For DEFAULT responseType we use this to keep track of how far we've
+ // lazily decoded from mResponseBody to mResponseText
+ uint32_t mResponseBodyDecodedPos;
+
+ // Decoder used for decoding into mResponseText
+ // Only used for DEFAULT, TEXT and JSON responseTypes.
+ // In cases where we've only received half a surrogate, the decoder itself
+ // carries the state to remember this. Next time we receive more data we
+ // simply feed the new data into the decoder which will handle the second
+ // part of the surrogate.
+ nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+
+ nsCString mResponseCharset;
+
+ void MatchCharsetAndDecoderToResponseDocument();
+
+ XMLHttpRequestResponseType mResponseType;
+
+ // It is either a cached blob-response from the last call to GetResponse,
+ // but is also explicitly set in OnStopRequest.
+ RefPtr<Blob> mResponseBlob;
+ // Non-null only when we are able to get a os-file representation of the
+ // response, i.e. when loading from a file.
+ RefPtr<Blob> mDOMBlob;
+ // We stream data to mBlobStorage when response type is "blob" and mDOMBlob is
+ // null.
+ RefPtr<MutableBlobStorage> mBlobStorage;
+ // We stream data to mBlobStorage when response type is "moz-blob" and
+ // mDOMBlob is null.
+ nsAutoPtr<BlobSet> mBlobSet;
+
+ nsString mOverrideMimeType;
+
+ /**
+ * The notification callbacks the channel had when Send() was
+ * called. We want to forward things here as needed.
+ */
+ nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks;
+ /**
+ * Sink interfaces that we implement that mNotificationCallbacks may
+ * want to also be notified for. These are inited lazily if we're
+ * asked for the relevant interface.
+ */
+ nsCOMPtr<nsIChannelEventSink> mChannelEventSink;
+ nsCOMPtr<nsIProgressEventSink> mProgressEventSink;
+
+ nsIRequestObserver* mRequestObserver;
+
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+
+ State mState;
+
+ bool mFlagSynchronous;
+ bool mFlagAborted;
+ bool mFlagParseBody;
+ bool mFlagSyncLooping;
+ bool mFlagBackgroundRequest;
+ bool mFlagHadUploadListenersOnSend;
+ bool mFlagACwithCredentials;
+ bool mFlagTimedOut;
+ bool mFlagDeleted;
+
+ // The XHR2 spec's send() flag. Set when the XHR begins uploading, until it
+ // finishes downloading (or an error/abort has occurred during either phase).
+ // Used to guard against the user trying to alter headers/etc when it's too
+ // late, and ensure the XHR only handles one in-flight request at once.
+ bool mFlagSend;
+
+ RefPtr<XMLHttpRequestUpload> mUpload;
+ int64_t mUploadTransferred;
+ int64_t mUploadTotal;
+ bool mUploadComplete;
+ bool mProgressSinceLastProgressEvent;
+
+ // Timeout support
+ PRTime mRequestSentTime;
+ uint32_t mTimeoutMilliseconds;
+ nsCOMPtr<nsITimer> mTimeoutTimer;
+ void StartTimeoutTimer();
+ void HandleTimeoutCallback();
+
+ nsCOMPtr<nsITimer> mSyncTimeoutTimer;
+
+ enum SyncTimeoutType {
+ eErrorOrExpired,
+ eTimerStarted,
+ eNoTimerNeeded
+ };
+
+ SyncTimeoutType MaybeStartSyncTimeoutTimer();
+ void HandleSyncTimeoutTimer();
+ void CancelSyncTimeoutTimer();
+
+ bool mErrorLoad;
+ bool mErrorParsingXML;
+ bool mWaitingForOnStopRequest;
+ bool mProgressTimerIsActive;
+ bool mIsHtml;
+ bool mWarnAboutMultipartHtml;
+ bool mWarnAboutSyncHtml;
+ int64_t mLoadTotal; // -1 if not known.
+ // Amount of script-exposed (i.e. after undoing gzip compresion) data
+ // received.
+ uint64_t mDataAvailable;
+ // Number of HTTP message body bytes received so far. This quantity is
+ // in the same units as Content-Length and mLoadTotal, and hence counts
+ // compressed bytes when the channel has gzip Content-Encoding. If the
+ // channel does not have Content-Encoding, this will be the same as
+ // mDataReceived except between the OnProgress that changes mLoadTransferred
+ // and the corresponding OnDataAvailable (which changes mDataReceived).
+ // Ordering of OnProgress and OnDataAvailable is undefined.
+ int64_t mLoadTransferred;
+ nsCOMPtr<nsITimer> mProgressNotifier;
+ void HandleProgressTimerCallback();
+
+ bool mIsSystem;
+ bool mIsAnon;
+
+ // A platform-specific identifer to represent the network interface
+ // that this request is associated with.
+ nsCString mNetworkInterfaceId;
+
+ /**
+ * Close the XMLHttpRequest's channels.
+ */
+ void CloseRequest();
+
+ /**
+ * Close the XMLHttpRequest's channels and dispatch appropriate progress
+ * events.
+ *
+ * @param aType The progress event type.
+ */
+ void CloseRequestWithError(const ProgressEventType aType);
+
+ bool mFirstStartRequestSeen;
+ bool mInLoadProgressEvent;
+
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+ nsCOMPtr<nsIChannel> mNewRedirectChannel;
+
+ JS::Heap<JS::Value> mResultJSON;
+
+ ArrayBufferBuilder mArrayBufferBuilder;
+ JS::Heap<JSObject*> mResultArrayBuffer;
+ bool mIsMappedArrayBuffer;
+
+ void ResetResponse();
+
+ bool ShouldBlockAuthPrompt();
+
+ RequestHeaders mAuthorRequestHeaders;
+
+ // Helper object to manage our XPCOM scriptability bits
+ nsXMLHttpRequestXPCOMifier* mXPCOMifier;
+
+ static bool sDontWarnAboutSyncXHR;
+};
+
+class MOZ_STACK_CLASS AutoDontWarnAboutSyncXHR
+{
+public:
+ AutoDontWarnAboutSyncXHR() : mOldVal(XMLHttpRequestMainThread::DontWarnAboutSyncXHR())
+ {
+ XMLHttpRequestMainThread::SetDontWarnAboutSyncXHR(true);
+ }
+
+ ~AutoDontWarnAboutSyncXHR()
+ {
+ XMLHttpRequestMainThread::SetDontWarnAboutSyncXHR(mOldVal);
+ }
+
+private:
+ bool mOldVal;
+};
+
+// A shim class designed to expose the non-DOM interfaces of
+// XMLHttpRequest via XPCOM stuff.
+class nsXMLHttpRequestXPCOMifier final : public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIAsyncVerifyRedirectCallback,
+ public nsIProgressEventSink,
+ public nsIInterfaceRequestor,
+ public nsITimerCallback
+{
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXMLHttpRequestXPCOMifier,
+ nsIStreamListener)
+
+ explicit nsXMLHttpRequestXPCOMifier(XMLHttpRequestMainThread* aXHR) :
+ mXHR(aXHR)
+ {
+ }
+
+private:
+ ~nsXMLHttpRequestXPCOMifier() {
+ if (mXHR) {
+ mXHR->mXPCOMifier = nullptr;
+ }
+ }
+
+public:
+ NS_FORWARD_NSISTREAMLISTENER(mXHR->)
+ NS_FORWARD_NSIREQUESTOBSERVER(mXHR->)
+ NS_FORWARD_NSICHANNELEVENTSINK(mXHR->)
+ NS_FORWARD_NSIASYNCVERIFYREDIRECTCALLBACK(mXHR->)
+ NS_FORWARD_NSIPROGRESSEVENTSINK(mXHR->)
+ NS_FORWARD_NSITIMERCALLBACK(mXHR->)
+
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+private:
+ RefPtr<XMLHttpRequestMainThread> mXHR;
+};
+
+class nsXHRParseEndListener : public nsIDOMEventListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD HandleEvent(nsIDOMEvent *event) override
+ {
+ nsCOMPtr<nsIXMLHttpRequest> xhr = do_QueryReferent(mXHR);
+ if (xhr) {
+ static_cast<XMLHttpRequestMainThread*>(xhr.get())->OnBodyParseEnd();
+ }
+ mXHR = nullptr;
+ return NS_OK;
+ }
+ explicit nsXHRParseEndListener(nsIXMLHttpRequest* aXHR)
+ : mXHR(do_GetWeakReference(aXHR)) {}
+private:
+ virtual ~nsXHRParseEndListener() {}
+
+ nsWeakPtr mXHR;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_XMLHttpRequestMainThread_h
diff --git a/dom/xhr/XMLHttpRequestString.cpp b/dom/xhr/XMLHttpRequestString.cpp
new file mode 100644
index 000000000..c668b94f6
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestString.cpp
@@ -0,0 +1,267 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "XMLHttpRequestString.h"
+#include "mozilla/Mutex.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/dom/DOMString.h"
+
+namespace mozilla {
+namespace dom {
+
+class XMLHttpRequestStringBuffer final
+{
+ friend class XMLHttpRequestStringWriterHelper;
+ friend class XMLHttpRequestStringSnapshotReaderHelper;
+
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(XMLHttpRequestStringBuffer)
+ NS_DECL_OWNINGTHREAD
+
+ XMLHttpRequestStringBuffer()
+ : mMutex("XMLHttpRequestStringBuffer::mMutex")
+ {
+ }
+
+ uint32_t
+ Length()
+ {
+ MutexAutoLock lock(mMutex);
+ return mData.Length();
+ }
+
+ uint32_t
+ UnsafeLength() const
+ {
+ return mData.Length();
+ }
+
+ void
+ Append(const nsAString& aString)
+ {
+ NS_ASSERT_OWNINGTHREAD(XMLHttpRequestStringBuffer);
+
+ MutexAutoLock lock(mMutex);
+ mData.Append(aString);
+ }
+
+ MOZ_MUST_USE bool
+ GetAsString(nsAString& aString)
+ {
+ MutexAutoLock lock(mMutex);
+ return aString.Assign(mData, mozilla::fallible);
+ }
+
+ size_t
+ SizeOfThis(MallocSizeOf aMallocSizeOf) const
+ {
+ return mData.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+
+ MOZ_MUST_USE bool
+ GetAsString(DOMString& aString, uint32_t aLength)
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(aLength <= mData.Length());
+ nsStringBuffer* buf = nsStringBuffer::FromString(mData);
+ if (buf) {
+ // We have to use SetEphemeralStringBuffer, because once we release our
+ // mutex mData can get mutated from some other thread while the DOMString
+ // is still alive.
+ aString.SetEphemeralStringBuffer(buf, aLength);
+ return true;
+ }
+
+ // We can get here if mData is empty. In that case it won't have an
+ // nsStringBuffer....
+ MOZ_ASSERT(mData.IsEmpty());
+ return aString.AsAString().Assign(mData.BeginReading(), aLength,
+ mozilla::fallible);
+ }
+
+ void
+ CreateSnapshot(XMLHttpRequestStringSnapshot& aSnapshot)
+ {
+ MutexAutoLock lock(mMutex);
+ aSnapshot.Set(this, mData.Length());
+ }
+
+private:
+ ~XMLHttpRequestStringBuffer()
+ {}
+
+ nsString& UnsafeData()
+ {
+ return mData;
+ }
+
+ Mutex mMutex;
+
+ // The following member variable is protected by mutex.
+ nsString mData;
+};
+
+// ---------------------------------------------------------------------------
+// XMLHttpRequestString
+
+XMLHttpRequestString::XMLHttpRequestString()
+ : mBuffer(new XMLHttpRequestStringBuffer())
+{
+}
+
+XMLHttpRequestString::~XMLHttpRequestString()
+{
+}
+
+void
+XMLHttpRequestString::Truncate()
+{
+ mBuffer = new XMLHttpRequestStringBuffer();
+}
+
+uint32_t
+XMLHttpRequestString::Length() const
+{
+ return mBuffer->Length();
+}
+
+void
+XMLHttpRequestString::Append(const nsAString& aString)
+{
+ mBuffer->Append(aString);
+}
+
+bool
+XMLHttpRequestString::GetAsString(nsAString& aString) const
+{
+ return mBuffer->GetAsString(aString);
+}
+
+size_t
+XMLHttpRequestString::SizeOfThis(MallocSizeOf aMallocSizeOf) const
+{
+ return mBuffer->SizeOfThis(aMallocSizeOf);
+}
+
+bool
+XMLHttpRequestString::IsEmpty() const
+{
+ return !mBuffer->Length();
+}
+
+void
+XMLHttpRequestString::CreateSnapshot(XMLHttpRequestStringSnapshot& aSnapshot)
+{
+ mBuffer->CreateSnapshot(aSnapshot);
+}
+
+// ---------------------------------------------------------------------------
+// XMLHttpRequestStringSnapshot
+
+XMLHttpRequestStringSnapshot::XMLHttpRequestStringSnapshot()
+ : mLength(0)
+ , mVoid(false)
+{
+}
+
+XMLHttpRequestStringSnapshot::~XMLHttpRequestStringSnapshot()
+{
+}
+
+XMLHttpRequestStringSnapshot&
+XMLHttpRequestStringSnapshot::operator=(const XMLHttpRequestStringSnapshot& aOther)
+{
+ mBuffer = aOther.mBuffer;
+ mLength = aOther.mLength;
+ mVoid = aOther.mVoid;
+ return *this;
+}
+
+void
+XMLHttpRequestStringSnapshot::ResetInternal(bool aIsVoid)
+{
+ mBuffer = nullptr;
+ mLength = 0;
+ mVoid = aIsVoid;
+}
+
+void
+XMLHttpRequestStringSnapshot::Set(XMLHttpRequestStringBuffer* aBuffer,
+ uint32_t aLength)
+{
+ MOZ_ASSERT(aBuffer);
+ MOZ_ASSERT(aLength <= aBuffer->UnsafeLength());
+
+ mBuffer = aBuffer;
+ mLength = aLength;
+ mVoid = false;
+}
+
+bool
+XMLHttpRequestStringSnapshot::GetAsString(DOMString& aString) const
+{
+ if (mBuffer) {
+ MOZ_ASSERT(!mVoid);
+ return mBuffer->GetAsString(aString, mLength);
+ }
+
+ if (mVoid) {
+ aString.SetNull();
+ }
+
+ return true;
+}
+
+// ---------------------------------------------------------------------------
+// XMLHttpRequestStringWriterHelper
+
+XMLHttpRequestStringWriterHelper::XMLHttpRequestStringWriterHelper(XMLHttpRequestString& aString)
+ : mBuffer(aString.mBuffer)
+ , mLock(aString.mBuffer->mMutex)
+{
+}
+
+bool
+XMLHttpRequestStringWriterHelper::AddCapacity(int32_t aCapacity)
+{
+ return mBuffer->UnsafeData().SetCapacity(mBuffer->UnsafeLength() + aCapacity, fallible);
+}
+
+char16_t*
+XMLHttpRequestStringWriterHelper::EndOfExistingData()
+{
+ return mBuffer->UnsafeData().BeginWriting() + mBuffer->UnsafeLength();
+}
+
+void
+XMLHttpRequestStringWriterHelper::AddLength(int32_t aLength)
+{
+ mBuffer->UnsafeData().SetLength(mBuffer->UnsafeLength() + aLength);
+}
+
+// ---------------------------------------------------------------------------
+// XMLHttpRequestStringReaderHelper
+
+XMLHttpRequestStringSnapshotReaderHelper::XMLHttpRequestStringSnapshotReaderHelper(XMLHttpRequestStringSnapshot& aSnapshot)
+ : mBuffer(aSnapshot.mBuffer)
+ , mLock(aSnapshot.mBuffer->mMutex)
+{
+}
+
+const char16_t*
+XMLHttpRequestStringSnapshotReaderHelper::Buffer() const
+{
+ return mBuffer->UnsafeData().BeginReading();
+}
+
+uint32_t
+XMLHttpRequestStringSnapshotReaderHelper::Length() const
+{
+ return mBuffer->UnsafeLength();
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/xhr/XMLHttpRequestString.h b/dom/xhr/XMLHttpRequestString.h
new file mode 100644
index 000000000..b6b12483f
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestString.h
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_XMLHttpRequestString_h
+#define mozilla_dom_XMLHttpRequestString_h
+
+#include "nsString.h"
+
+namespace mozilla {
+
+class Mutex;
+
+namespace dom {
+
+class DOMString;
+class XMLHttpRequestStringBuffer;
+class XMLHttpRequestStringSnapshot;
+class XMLHttpRequestStringWriterHelper;
+class XMLHttpRequestStringSnapshotReaderHelper;
+
+// We want to avoid the dup of strings when XHR in workers has access to
+// responseText for events dispatched during the loading state. For this reason
+// we use this class, able to create snapshots of the current size of itself
+// without making extra copies.
+class XMLHttpRequestString final
+{
+ friend class XMLHttpRequestStringWriterHelper;
+
+public:
+ XMLHttpRequestString();
+ ~XMLHttpRequestString();
+
+ void Truncate();
+
+ uint32_t Length() const;
+
+ void Append(const nsAString& aString);
+
+ // This method should be called only when the string is really needed because
+ // it can cause the duplication of the strings in case the loading of the XHR
+ // is not completed yet.
+ MOZ_MUST_USE bool GetAsString(nsAString& aString) const;
+
+ size_t SizeOfThis(MallocSizeOf aMallocSizeOf) const;
+
+ const char16_t* Data() const;
+
+ bool IsEmpty() const;
+
+ void CreateSnapshot(XMLHttpRequestStringSnapshot& aSnapshot);
+
+private:
+ XMLHttpRequestString(const XMLHttpRequestString&) = delete;
+ XMLHttpRequestString& operator=(const XMLHttpRequestString&) = delete;
+ XMLHttpRequestString& operator=(const XMLHttpRequestString&&) = delete;
+
+ RefPtr<XMLHttpRequestStringBuffer> mBuffer;
+};
+
+// This class locks the buffer and allows the callee to write data into it.
+class MOZ_STACK_CLASS XMLHttpRequestStringWriterHelper final
+{
+public:
+ explicit XMLHttpRequestStringWriterHelper(XMLHttpRequestString& aString);
+
+ bool
+ AddCapacity(int32_t aCapacity);
+
+ char16_t*
+ EndOfExistingData();
+
+ void
+ AddLength(int32_t aLength);
+
+private:
+ XMLHttpRequestStringWriterHelper(const XMLHttpRequestStringWriterHelper&) = delete;
+ XMLHttpRequestStringWriterHelper& operator=(const XMLHttpRequestStringWriterHelper&) = delete;
+ XMLHttpRequestStringWriterHelper& operator=(const XMLHttpRequestStringWriterHelper&&) = delete;
+
+ RefPtr<XMLHttpRequestStringBuffer> mBuffer;
+ MutexAutoLock mLock;
+};
+
+// This class is the internal XMLHttpRequestStringBuffer of the
+// XMLHttpRequestString plus the current length. GetAsString will return the
+// string with that particular length also if the XMLHttpRequestStringBuffer is
+// grown in the meantime.
+class XMLHttpRequestStringSnapshot final
+{
+ friend class XMLHttpRequestStringBuffer;
+ friend class XMLHttpRequestStringSnapshotReaderHelper;
+
+public:
+ XMLHttpRequestStringSnapshot();
+ ~XMLHttpRequestStringSnapshot();
+
+ XMLHttpRequestStringSnapshot& operator=(const XMLHttpRequestStringSnapshot&);
+
+ void Reset()
+ {
+ ResetInternal(false /* isVoid */);
+ }
+
+ void SetVoid()
+ {
+ ResetInternal(true /* isVoid */);
+ }
+
+ bool IsVoid() const
+ {
+ return mVoid;
+ }
+
+ bool IsEmpty() const
+ {
+ return !mLength;
+ }
+
+ MOZ_MUST_USE bool GetAsString(DOMString& aString) const;
+
+private:
+ XMLHttpRequestStringSnapshot(const XMLHttpRequestStringSnapshot&) = delete;
+ XMLHttpRequestStringSnapshot& operator=(const XMLHttpRequestStringSnapshot&&) = delete;
+
+ void Set(XMLHttpRequestStringBuffer* aBuffer, uint32_t aLength);
+
+ void ResetInternal(bool aIsVoid);
+
+ RefPtr<XMLHttpRequestStringBuffer> mBuffer;
+ uint32_t mLength;
+ bool mVoid;
+};
+
+// This class locks the buffer and allows the callee to read data from it.
+class MOZ_STACK_CLASS XMLHttpRequestStringSnapshotReaderHelper final
+{
+public:
+ explicit XMLHttpRequestStringSnapshotReaderHelper(XMLHttpRequestStringSnapshot& aSnapshot);
+
+ const char16_t*
+ Buffer() const;
+
+ uint32_t
+ Length() const;
+
+private:
+ XMLHttpRequestStringSnapshotReaderHelper(const XMLHttpRequestStringSnapshotReaderHelper&) = delete;
+ XMLHttpRequestStringSnapshotReaderHelper& operator=(const XMLHttpRequestStringSnapshotReaderHelper&) = delete;
+ XMLHttpRequestStringSnapshotReaderHelper& operator=(const XMLHttpRequestStringSnapshotReaderHelper&&) = delete;
+
+ RefPtr<XMLHttpRequestStringBuffer> mBuffer;
+ MutexAutoLock mLock;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_XMLHttpRequestString_h
diff --git a/dom/xhr/XMLHttpRequestUpload.cpp b/dom/xhr/XMLHttpRequestUpload.cpp
new file mode 100644
index 000000000..4de85c46c
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestUpload.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "XMLHttpRequestUpload.h"
+#include "mozilla/dom/XMLHttpRequestUploadBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_INTERFACE_MAP_BEGIN(XMLHttpRequestUpload)
+ NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequestUpload)
+NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
+
+NS_IMPL_ADDREF_INHERITED(XMLHttpRequestUpload, XMLHttpRequestEventTarget)
+NS_IMPL_RELEASE_INHERITED(XMLHttpRequestUpload, XMLHttpRequestEventTarget)
+
+/* virtual */ JSObject*
+XMLHttpRequestUpload::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return XMLHttpRequestUploadBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/xhr/XMLHttpRequestUpload.h b/dom/xhr/XMLHttpRequestUpload.h
new file mode 100644
index 000000000..fc83f5eac
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestUpload.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_XMLHttpRequestUpload_h
+#define mozilla_dom_XMLHttpRequestUpload_h
+
+#include "mozilla/dom/XMLHttpRequestEventTarget.h"
+#include "nsIXMLHttpRequest.h"
+
+namespace mozilla {
+namespace dom {
+
+class XMLHttpRequestUpload final : public XMLHttpRequestEventTarget,
+ public nsIXMLHttpRequestUpload
+{
+public:
+ explicit XMLHttpRequestUpload(DOMEventTargetHelper* aOwner)
+ : XMLHttpRequestEventTarget(aOwner)
+ {}
+
+ explicit XMLHttpRequestUpload()
+ {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSIXMLHTTPREQUESTEVENTTARGET(XMLHttpRequestEventTarget::)
+ NS_REALLY_FORWARD_NSIDOMEVENTTARGET(XMLHttpRequestEventTarget)
+ NS_DECL_NSIXMLHTTPREQUESTUPLOAD
+
+ virtual JSObject*
+ WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+ bool HasListeners()
+ {
+ return mListenerManager && mListenerManager->HasListeners();
+ }
+
+private:
+ virtual ~XMLHttpRequestUpload()
+ {}
+};
+
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_XMLHttpRequestUpload_h
+
diff --git a/dom/xhr/XMLHttpRequestWorker.cpp b/dom/xhr/XMLHttpRequestWorker.cpp
new file mode 100644
index 000000000..f61383baf
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestWorker.cpp
@@ -0,0 +1,2455 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "XMLHttpRequestWorker.h"
+
+#include "nsIDOMEvent.h"
+#include "nsIDOMEventListener.h"
+#include "nsIRunnable.h"
+#include "nsIXMLHttpRequest.h"
+#include "nsIXPConnect.h"
+
+#include "jsfriendapi.h"
+#include "js/TracingAPI.h"
+#include "js/GCPolicyAPI.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/ProgressEvent.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/Telemetry.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsJSUtils.h"
+#include "nsThreadUtils.h"
+#include "nsVariant.h"
+
+#include "RuntimeService.h"
+#include "WorkerScope.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "XMLHttpRequestUpload.h"
+
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+/* static */ void
+XMLHttpRequestWorker::StateData::trace(JSTracer *aTrc)
+{
+ JS::TraceEdge(aTrc, &mResponse, "XMLHttpRequestWorker::StateData::mResponse");
+}
+
+/**
+ * XMLHttpRequest in workers
+ *
+ * XHR in workers is implemented by proxying calls/events/etc between the
+ * worker thread and an XMLHttpRequest on the main thread. The glue
+ * object here is the Proxy, which lives on both threads. All other objects
+ * live on either the main thread (the XMLHttpRequest) or the worker thread
+ * (the worker and XHR private objects).
+ *
+ * The main thread XHR is always operated in async mode, even for sync XHR
+ * in workers. Calls made on the worker thread are proxied to the main thread
+ * synchronously (meaning the worker thread is blocked until the call
+ * returns). Each proxied call spins up a sync queue, which captures any
+ * synchronously dispatched events and ensures that they run synchronously
+ * on the worker as well. Asynchronously dispatched events are posted to the
+ * worker thread to run asynchronously. Some of the XHR state is mirrored on
+ * the worker thread to avoid needing a cross-thread call on every property
+ * access.
+ *
+ * The XHR private is stored in the private slot of the XHR JSObject on the
+ * worker thread. It is destroyed when that JSObject is GCd. The private
+ * roots its JSObject while network activity is in progress. It also
+ * adds itself as a feature to the worker to give itself a chance to clean up
+ * if the worker goes away during an XHR call. It is important that the
+ * rooting and feature registration (collectively called pinning) happens at
+ * the proper times. If we pin for too long we can cause memory leaks or even
+ * shutdown hangs. If we don't pin for long enough we introduce a GC hazard.
+ *
+ * The XHR is pinned from the time Send is called to roughly the time loadend
+ * is received. There are some complications involved with Abort and XHR
+ * reuse. We maintain a counter on the main thread of how many times Send was
+ * called on this XHR, and we decrement the counter every time we receive a
+ * loadend event. When the counter reaches zero we dispatch a runnable to the
+ * worker thread to unpin the XHR. We only decrement the counter if the
+ * dispatch was successful, because the worker may no longer be accepting
+ * regular runnables. In the event that we reach Proxy::Teardown and there
+ * the outstanding Send count is still non-zero, we dispatch a control
+ * runnable which is guaranteed to run.
+ *
+ * NB: Some of this could probably be simplified now that we have the
+ * inner/outer channel ids.
+ */
+
+class Proxy final : public nsIDOMEventListener
+{
+public:
+ // Read on multiple threads.
+ WorkerPrivate* mWorkerPrivate;
+ XMLHttpRequestWorker* mXMLHttpRequestPrivate;
+
+ // XHR Params:
+ bool mMozAnon;
+ bool mMozSystem;
+
+ // Only touched on the main thread.
+ RefPtr<XMLHttpRequestMainThread> mXHR;
+ nsCOMPtr<nsIXMLHttpRequestUpload> mXHRUpload;
+ nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+ nsCOMPtr<nsIEventTarget> mSyncEventResponseTarget;
+ uint32_t mInnerEventStreamId;
+ uint32_t mInnerChannelId;
+ uint32_t mOutstandingSendCount;
+
+ // Only touched on the worker thread.
+ uint32_t mOuterEventStreamId;
+ uint32_t mOuterChannelId;
+ uint32_t mOpenCount;
+ uint64_t mLastLoaded;
+ uint64_t mLastTotal;
+ uint64_t mLastUploadLoaded;
+ uint64_t mLastUploadTotal;
+ bool mIsSyncXHR;
+ bool mLastLengthComputable;
+ bool mLastUploadLengthComputable;
+ bool mSeenLoadStart;
+ bool mSeenUploadLoadStart;
+
+ // Only touched on the main thread.
+ bool mUploadEventListenersAttached;
+ bool mMainThreadSeenLoadStart;
+ bool mInOpen;
+ bool mArrayBufferResponseWasTransferred;
+
+public:
+ Proxy(XMLHttpRequestWorker* aXHRPrivate, bool aMozAnon, bool aMozSystem)
+ : mWorkerPrivate(nullptr), mXMLHttpRequestPrivate(aXHRPrivate),
+ mMozAnon(aMozAnon), mMozSystem(aMozSystem),
+ mInnerEventStreamId(0), mInnerChannelId(0), mOutstandingSendCount(0),
+ mOuterEventStreamId(0), mOuterChannelId(0), mOpenCount(0), mLastLoaded(0),
+ mLastTotal(0), mLastUploadLoaded(0), mLastUploadTotal(0), mIsSyncXHR(false),
+ mLastLengthComputable(false), mLastUploadLengthComputable(false),
+ mSeenLoadStart(false), mSeenUploadLoadStart(false),
+ mUploadEventListenersAttached(false), mMainThreadSeenLoadStart(false),
+ mInOpen(false), mArrayBufferResponseWasTransferred(false)
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ bool
+ Init();
+
+ void
+ Teardown(bool aSendUnpin);
+
+ bool
+ AddRemoveEventListeners(bool aUpload, bool aAdd);
+
+ void
+ Reset()
+ {
+ AssertIsOnMainThread();
+
+ if (mUploadEventListenersAttached) {
+ AddRemoveEventListeners(true, false);
+ }
+ }
+
+ already_AddRefed<nsIEventTarget>
+ GetEventTarget()
+ {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIEventTarget> target = mSyncEventResponseTarget ?
+ mSyncEventResponseTarget :
+ mSyncLoopTarget;
+ return target.forget();
+ }
+
+private:
+ ~Proxy()
+ {
+ MOZ_ASSERT(!mXHR);
+ MOZ_ASSERT(!mXHRUpload);
+ MOZ_ASSERT(!mOutstandingSendCount);
+ }
+};
+
+class WorkerThreadProxySyncRunnable : public WorkerMainThreadRunnable
+{
+protected:
+ RefPtr<Proxy> mProxy;
+
+private:
+ // mErrorCode is set on the main thread by MainThreadRun and it's used to at
+ // the end of the Dispatch() to return the error code.
+ nsresult mErrorCode;
+
+public:
+ WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+ : WorkerMainThreadRunnable(aWorkerPrivate, NS_LITERAL_CSTRING("XHR"))
+ , mProxy(aProxy)
+ , mErrorCode(NS_OK)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ MOZ_ASSERT(aProxy);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ void
+ Dispatch(ErrorResult& aRv)
+ {
+ WorkerMainThreadRunnable::Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (NS_FAILED(mErrorCode)) {
+ aRv.Throw(mErrorCode);
+ }
+ }
+
+protected:
+ virtual ~WorkerThreadProxySyncRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) = 0;
+
+private:
+ virtual bool MainThreadRun() override;
+};
+
+class SendRunnable final
+ : public WorkerThreadProxySyncRunnable
+ , public StructuredCloneHolder
+{
+ nsString mStringBody;
+ nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+ bool mHasUploadListeners;
+
+public:
+ SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ const nsAString& aStringBody)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+ , StructuredCloneHolder(CloningSupported, TransferringNotSupported,
+ StructuredCloneScope::SameProcessDifferentThread)
+ , mStringBody(aStringBody)
+ , mHasUploadListeners(false)
+ {
+ }
+
+ void SetHaveUploadListeners(bool aHasUploadListeners)
+ {
+ mHasUploadListeners = aHasUploadListeners;
+ }
+
+ void SetSyncLoopTarget(nsIEventTarget* aSyncLoopTarget)
+ {
+ mSyncLoopTarget = aSyncLoopTarget;
+ }
+
+private:
+ ~SendRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override;
+};
+
+namespace {
+
+enum
+{
+ STRING_abort = 0,
+ STRING_error,
+ STRING_load,
+ STRING_loadstart,
+ STRING_progress,
+ STRING_timeout,
+ STRING_readystatechange,
+ STRING_loadend,
+
+ STRING_COUNT,
+
+ STRING_LAST_XHR = STRING_loadend,
+ STRING_LAST_EVENTTARGET = STRING_timeout
+};
+
+static_assert(STRING_LAST_XHR >= STRING_LAST_EVENTTARGET, "Bad string setup!");
+static_assert(STRING_LAST_XHR == STRING_COUNT - 1, "Bad string setup!");
+
+const char* const sEventStrings[] = {
+ // nsIXMLHttpRequestEventTarget event types, supported by both XHR and Upload.
+ "abort",
+ "error",
+ "load",
+ "loadstart",
+ "progress",
+ "timeout",
+
+ // nsIXMLHttpRequest event types, supported only by XHR.
+ "readystatechange",
+ "loadend",
+};
+
+static_assert(MOZ_ARRAY_LENGTH(sEventStrings) == STRING_COUNT,
+ "Bad string count!");
+
+class MainThreadProxyRunnable : public MainThreadWorkerSyncRunnable
+{
+protected:
+ RefPtr<Proxy> mProxy;
+
+ MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+ : MainThreadWorkerSyncRunnable(aWorkerPrivate, aProxy->GetEventTarget()),
+ mProxy(aProxy)
+ {
+ MOZ_ASSERT(aProxy);
+ }
+
+ virtual ~MainThreadProxyRunnable()
+ { }
+};
+
+class XHRUnpinRunnable final : public MainThreadWorkerControlRunnable
+{
+ XMLHttpRequestWorker* mXMLHttpRequestPrivate;
+
+public:
+ XHRUnpinRunnable(WorkerPrivate* aWorkerPrivate,
+ XMLHttpRequestWorker* aXHRPrivate)
+ : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ mXMLHttpRequestPrivate(aXHRPrivate)
+ {
+ MOZ_ASSERT(aXHRPrivate);
+ }
+
+private:
+ ~XHRUnpinRunnable()
+ { }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+ {
+ if (mXMLHttpRequestPrivate->SendInProgress()) {
+ mXMLHttpRequestPrivate->Unpin();
+ }
+
+ return true;
+ }
+};
+
+class AsyncTeardownRunnable final : public Runnable
+{
+ RefPtr<Proxy> mProxy;
+
+public:
+ explicit AsyncTeardownRunnable(Proxy* aProxy)
+ : mProxy(aProxy)
+ {
+ MOZ_ASSERT(aProxy);
+ }
+
+private:
+ ~AsyncTeardownRunnable()
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ AssertIsOnMainThread();
+
+ // This means the XHR was GC'd, so we can't be pinned, and we don't need to
+ // try to unpin.
+ mProxy->Teardown(/* aSendUnpin */ false);
+ mProxy = nullptr;
+
+ return NS_OK;
+ }
+};
+
+class LoadStartDetectionRunnable final : public Runnable,
+ public nsIDOMEventListener
+{
+ WorkerPrivate* mWorkerPrivate;
+ RefPtr<Proxy> mProxy;
+ RefPtr<XMLHttpRequest> mXHR;
+ XMLHttpRequestWorker* mXMLHttpRequestPrivate;
+ nsString mEventType;
+ uint32_t mChannelId;
+ bool mReceivedLoadStart;
+
+ class ProxyCompleteRunnable final : public MainThreadProxyRunnable
+ {
+ XMLHttpRequestWorker* mXMLHttpRequestPrivate;
+ uint32_t mChannelId;
+
+ public:
+ ProxyCompleteRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ XMLHttpRequestWorker* aXHRPrivate, uint32_t aChannelId)
+ : MainThreadProxyRunnable(aWorkerPrivate, aProxy),
+ mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(aChannelId)
+ { }
+
+ private:
+ ~ProxyCompleteRunnable()
+ { }
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ if (mChannelId != mProxy->mOuterChannelId) {
+ // Threads raced, this event is now obsolete.
+ return true;
+ }
+
+ if (mSyncLoopTarget) {
+ aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, true);
+ }
+
+ if (mXMLHttpRequestPrivate->SendInProgress()) {
+ mXMLHttpRequestPrivate->Unpin();
+ }
+
+ return true;
+ }
+
+ nsresult
+ Cancel() override
+ {
+ // This must run!
+ nsresult rv = MainThreadProxyRunnable::Cancel();
+ nsresult rv2 = Run();
+ return NS_FAILED(rv) ? rv : rv2;
+ }
+ };
+
+public:
+ LoadStartDetectionRunnable(Proxy* aProxy, XMLHttpRequestWorker* aXHRPrivate)
+ : mWorkerPrivate(aProxy->mWorkerPrivate), mProxy(aProxy), mXHR(aProxy->mXHR),
+ mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(mProxy->mInnerChannelId),
+ mReceivedLoadStart(false)
+ {
+ AssertIsOnMainThread();
+ mEventType.AssignWithConversion(sEventStrings[STRING_loadstart]);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ bool
+ RegisterAndDispatch()
+ {
+ AssertIsOnMainThread();
+
+ if (NS_FAILED(mXHR->AddEventListener(mEventType, this, false, false, 2))) {
+ NS_WARNING("Failed to add event listener!");
+ return false;
+ }
+
+ return NS_SUCCEEDED(NS_DispatchToCurrentThread(this));
+ }
+
+private:
+ ~LoadStartDetectionRunnable()
+ {
+ AssertIsOnMainThread();
+ }
+};
+
+class EventRunnable final : public MainThreadProxyRunnable
+ , public StructuredCloneHolder
+{
+ nsString mType;
+ nsString mResponseType;
+ JS::Heap<JS::Value> mResponse;
+ XMLHttpRequestStringSnapshot mResponseText;
+ nsString mResponseURL;
+ nsCString mStatusText;
+ uint64_t mLoaded;
+ uint64_t mTotal;
+ uint32_t mEventStreamId;
+ uint32_t mStatus;
+ uint16_t mReadyState;
+ bool mUploadEvent;
+ bool mProgressEvent;
+ bool mLengthComputable;
+ bool mUseCachedArrayBufferResponse;
+ nsresult mResponseTextResult;
+ nsresult mStatusResult;
+ nsresult mResponseResult;
+ // mScopeObj is used in PreDispatch only. We init it in our constructor, and
+ // reset() in PreDispatch, to ensure that it's not still linked into the
+ // runtime once we go off-thread.
+ JS::PersistentRooted<JSObject*> mScopeObj;
+
+public:
+ EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
+ bool aLengthComputable, uint64_t aLoaded, uint64_t aTotal,
+ JS::Handle<JSObject*> aScopeObj)
+ : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy),
+ StructuredCloneHolder(CloningSupported, TransferringNotSupported,
+ StructuredCloneScope::SameProcessDifferentThread),
+ mType(aType), mResponse(JS::UndefinedValue()), mLoaded(aLoaded),
+ mTotal(aTotal), mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0),
+ mReadyState(0), mUploadEvent(aUploadEvent), mProgressEvent(true),
+ mLengthComputable(aLengthComputable), mUseCachedArrayBufferResponse(false),
+ mResponseTextResult(NS_OK), mStatusResult(NS_OK), mResponseResult(NS_OK),
+ mScopeObj(RootingCx(), aScopeObj)
+ { }
+
+ EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
+ JS::Handle<JSObject*> aScopeObj)
+ : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy),
+ StructuredCloneHolder(CloningSupported, TransferringNotSupported,
+ StructuredCloneScope::SameProcessDifferentThread),
+ mType(aType), mResponse(JS::UndefinedValue()), mLoaded(0), mTotal(0),
+ mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0),
+ mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0),
+ mUseCachedArrayBufferResponse(false), mResponseTextResult(NS_OK),
+ mStatusResult(NS_OK), mResponseResult(NS_OK),
+ mScopeObj(RootingCx(), aScopeObj)
+ { }
+
+private:
+ ~EventRunnable()
+ { }
+
+ virtual bool
+ PreDispatch(WorkerPrivate* /* unused */) override final;
+
+ virtual bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+};
+
+class SyncTeardownRunnable final : public WorkerThreadProxySyncRunnable
+{
+public:
+ SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+ { }
+
+private:
+ ~SyncTeardownRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->Teardown(/* aSendUnpin */ true);
+ MOZ_ASSERT(!mProxy->mSyncLoopTarget);
+ }
+};
+
+class SetBackgroundRequestRunnable final :
+ public WorkerThreadProxySyncRunnable
+{
+ bool mValue;
+
+public:
+ SetBackgroundRequestRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ bool aValue)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+ , mValue(aValue)
+ { }
+
+private:
+ ~SetBackgroundRequestRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->SetMozBackgroundRequest(mValue, aRv);
+ }
+};
+
+class SetWithCredentialsRunnable final :
+ public WorkerThreadProxySyncRunnable
+{
+ bool mValue;
+
+public:
+ SetWithCredentialsRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ bool aValue)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+ , mValue(aValue)
+ { }
+
+private:
+ ~SetWithCredentialsRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->SetWithCredentials(mValue, aRv);
+ }
+};
+
+class SetResponseTypeRunnable final : public WorkerThreadProxySyncRunnable
+{
+ XMLHttpRequestResponseType mResponseType;
+
+public:
+ SetResponseTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ XMLHttpRequestResponseType aResponseType)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mResponseType(aResponseType)
+ { }
+
+ XMLHttpRequestResponseType
+ ResponseType()
+ {
+ return mResponseType;
+ }
+
+private:
+ ~SetResponseTypeRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->SetResponseType(mResponseType, aRv);
+ if (!aRv.Failed()) {
+ mResponseType = mProxy->mXHR->ResponseType();
+ }
+ }
+};
+
+class SetTimeoutRunnable final : public WorkerThreadProxySyncRunnable
+{
+ uint32_t mTimeout;
+
+public:
+ SetTimeoutRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ uint32_t aTimeout)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mTimeout(aTimeout)
+ { }
+
+private:
+ ~SetTimeoutRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->SetTimeout(mTimeout, aRv);
+ }
+};
+
+class AbortRunnable final : public WorkerThreadProxySyncRunnable
+{
+public:
+ AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+ { }
+
+private:
+ ~AbortRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override;
+};
+
+class GetAllResponseHeadersRunnable final :
+ public WorkerThreadProxySyncRunnable
+{
+ nsCString& mResponseHeaders;
+
+public:
+ GetAllResponseHeadersRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ nsCString& aResponseHeaders)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mResponseHeaders(aResponseHeaders)
+ { }
+
+private:
+ ~GetAllResponseHeadersRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->GetAllResponseHeaders(mResponseHeaders, aRv);
+ }
+};
+
+class GetResponseHeaderRunnable final : public WorkerThreadProxySyncRunnable
+{
+ const nsCString mHeader;
+ nsCString& mValue;
+
+public:
+ GetResponseHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ const nsACString& aHeader, nsCString& aValue)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mHeader(aHeader),
+ mValue(aValue)
+ { }
+
+private:
+ ~GetResponseHeaderRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->GetResponseHeader(mHeader, mValue, aRv);
+ }
+};
+
+class OpenRunnable final : public WorkerThreadProxySyncRunnable
+{
+ nsCString mMethod;
+ nsString mURL;
+ Optional<nsAString> mUser;
+ nsString mUserStr;
+ Optional<nsAString> mPassword;
+ nsString mPasswordStr;
+ bool mBackgroundRequest;
+ bool mWithCredentials;
+ uint32_t mTimeout;
+ XMLHttpRequestResponseType mResponseType;
+
+public:
+ OpenRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ const nsACString& aMethod, const nsAString& aURL,
+ const Optional<nsAString>& aUser,
+ const Optional<nsAString>& aPassword,
+ bool aBackgroundRequest, bool aWithCredentials,
+ uint32_t aTimeout, XMLHttpRequestResponseType aResponseType)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mMethod(aMethod),
+ mURL(aURL), mBackgroundRequest(aBackgroundRequest),
+ mWithCredentials(aWithCredentials), mTimeout(aTimeout),
+ mResponseType(aResponseType)
+ {
+ if (aUser.WasPassed()) {
+ mUserStr = aUser.Value();
+ mUser = &mUserStr;
+ }
+ if (aPassword.WasPassed()) {
+ mPasswordStr = aPassword.Value();
+ mPassword = &mPasswordStr;
+ }
+ }
+
+private:
+ ~OpenRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
+ mProxy->mWorkerPrivate = mWorkerPrivate;
+
+ aRv = MainThreadRunInternal();
+
+ mProxy->mWorkerPrivate = oldWorker;
+ }
+
+ nsresult
+ MainThreadRunInternal();
+};
+
+class SetRequestHeaderRunnable final : public WorkerThreadProxySyncRunnable
+{
+ nsCString mHeader;
+ nsCString mValue;
+
+public:
+ SetRequestHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ const nsACString& aHeader, const nsACString& aValue)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mHeader(aHeader),
+ mValue(aValue)
+ { }
+
+private:
+ ~SetRequestHeaderRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->SetRequestHeader(mHeader, mValue, aRv);
+ }
+};
+
+class OverrideMimeTypeRunnable final : public WorkerThreadProxySyncRunnable
+{
+ nsString mMimeType;
+
+public:
+ OverrideMimeTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+ const nsAString& aMimeType)
+ : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+ mMimeType(aMimeType)
+ { }
+
+private:
+ ~OverrideMimeTypeRunnable()
+ { }
+
+ virtual void
+ RunOnMainThread(ErrorResult& aRv) override
+ {
+ mProxy->mXHR->OverrideMimeType(mMimeType, aRv);
+ }
+};
+
+class AutoUnpinXHR
+{
+ XMLHttpRequestWorker* mXMLHttpRequestPrivate;
+
+public:
+ explicit AutoUnpinXHR(XMLHttpRequestWorker* aXMLHttpRequestPrivate)
+ : mXMLHttpRequestPrivate(aXMLHttpRequestPrivate)
+ {
+ MOZ_ASSERT(aXMLHttpRequestPrivate);
+ }
+
+ ~AutoUnpinXHR()
+ {
+ if (mXMLHttpRequestPrivate) {
+ mXMLHttpRequestPrivate->Unpin();
+ }
+ }
+
+ void Clear()
+ {
+ mXMLHttpRequestPrivate = nullptr;
+ }
+};
+
+} // namespace
+
+bool
+Proxy::Init()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mWorkerPrivate);
+
+ if (mXHR) {
+ return true;
+ }
+
+ nsPIDOMWindowInner* ownerWindow = mWorkerPrivate->GetWindow();
+ if (ownerWindow && !ownerWindow->IsCurrentInnerWindow()) {
+ NS_WARNING("Window has navigated, cannot create XHR here.");
+ return false;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(ownerWindow);
+
+ mXHR = new XMLHttpRequestMainThread();
+ mXHR->Construct(mWorkerPrivate->GetPrincipal(), global,
+ mWorkerPrivate->GetBaseURI(),
+ mWorkerPrivate->GetLoadGroup());
+
+ mXHR->SetParameters(mMozAnon, mMozSystem);
+
+ ErrorResult rv;
+ mXHRUpload = mXHR->GetUpload(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ mXHR = nullptr;
+ return false;
+ }
+
+ if (!AddRemoveEventListeners(false, true)) {
+ mXHR = nullptr;
+ mXHRUpload = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+void
+Proxy::Teardown(bool aSendUnpin)
+{
+ AssertIsOnMainThread();
+
+ if (mXHR) {
+ Reset();
+
+ // NB: We are intentionally dropping events coming from xhr.abort on the
+ // floor.
+ AddRemoveEventListeners(false, false);
+
+ ErrorResult rv;
+ mXHR->Abort(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+
+ if (mOutstandingSendCount) {
+ if (aSendUnpin) {
+ RefPtr<XHRUnpinRunnable> runnable =
+ new XHRUnpinRunnable(mWorkerPrivate, mXMLHttpRequestPrivate);
+ if (!runnable->Dispatch()) {
+ NS_RUNTIMEABORT("We're going to hang at shutdown anyways.");
+ }
+ }
+
+ if (mSyncLoopTarget) {
+ // We have an unclosed sync loop. Fix that now.
+ RefPtr<MainThreadStopSyncLoopRunnable> runnable =
+ new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
+ mSyncLoopTarget.forget(),
+ false);
+ if (!runnable->Dispatch()) {
+ NS_RUNTIMEABORT("We're going to hang at shutdown anyways.");
+ }
+ }
+
+ mOutstandingSendCount = 0;
+ }
+
+ mWorkerPrivate = nullptr;
+ mXHRUpload = nullptr;
+ mXHR = nullptr;
+ }
+
+ MOZ_ASSERT(!mWorkerPrivate);
+ MOZ_ASSERT(!mSyncLoopTarget);
+}
+
+bool
+Proxy::AddRemoveEventListeners(bool aUpload, bool aAdd)
+{
+ AssertIsOnMainThread();
+
+ NS_ASSERTION(!aUpload ||
+ (mUploadEventListenersAttached && !aAdd) ||
+ (!mUploadEventListenersAttached && aAdd),
+ "Messed up logic for upload listeners!");
+
+ nsCOMPtr<nsIDOMEventTarget> target =
+ aUpload ?
+ do_QueryInterface(mXHRUpload) :
+ do_QueryInterface(static_cast<nsIXMLHttpRequest*>(mXHR.get()));
+ NS_ASSERTION(target, "This should never fail!");
+
+ uint32_t lastEventType = aUpload ? STRING_LAST_EVENTTARGET : STRING_LAST_XHR;
+
+ nsAutoString eventType;
+ for (uint32_t index = 0; index <= lastEventType; index++) {
+ eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]);
+ if (aAdd) {
+ if (NS_FAILED(target->AddEventListener(eventType, this, false))) {
+ return false;
+ }
+ }
+ else if (NS_FAILED(target->RemoveEventListener(eventType, this, false))) {
+ return false;
+ }
+ }
+
+ if (aUpload) {
+ mUploadEventListenersAttached = aAdd;
+ }
+
+ return true;
+}
+
+NS_IMPL_ISUPPORTS(Proxy, nsIDOMEventListener)
+
+NS_IMETHODIMP
+Proxy::HandleEvent(nsIDOMEvent* aEvent)
+{
+ AssertIsOnMainThread();
+
+ if (!mWorkerPrivate || !mXMLHttpRequestPrivate) {
+ NS_ERROR("Shouldn't get here!");
+ return NS_OK;
+ }
+
+ nsString type;
+ if (NS_FAILED(aEvent->GetType(type))) {
+ NS_WARNING("Failed to get event type!");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDOMEventTarget> target;
+ if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target)))) {
+ NS_WARNING("Failed to get target!");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIXMLHttpRequestUpload> uploadTarget = do_QueryInterface(target);
+ ProgressEvent* progressEvent = aEvent->InternalDOMEvent()->AsProgressEvent();
+
+ if (mInOpen && type.EqualsASCII(sEventStrings[STRING_readystatechange])) {
+ uint16_t readyState = 0;
+ if (NS_SUCCEEDED(mXHR->GetReadyState(&readyState)) &&
+ readyState == nsIXMLHttpRequest::OPENED) {
+ mInnerEventStreamId++;
+ }
+ }
+
+ {
+ AutoSafeJSContext cx;
+ JSAutoRequest ar(cx);
+
+ JS::Rooted<JS::Value> value(cx);
+ if (!GetOrCreateDOMReflectorNoWrap(cx, mXHR, &value)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSObject*> scope(cx, &value.toObject());
+
+ RefPtr<EventRunnable> runnable;
+ if (progressEvent) {
+ runnable = new EventRunnable(this, !!uploadTarget, type,
+ progressEvent->LengthComputable(),
+ progressEvent->Loaded(),
+ progressEvent->Total(),
+ scope);
+ }
+ else {
+ runnable = new EventRunnable(this, !!uploadTarget, type, scope);
+ }
+
+ runnable->Dispatch();
+ }
+
+ if (!uploadTarget) {
+ if (type.EqualsASCII(sEventStrings[STRING_loadstart])) {
+ mMainThreadSeenLoadStart = true;
+ }
+ else if (mMainThreadSeenLoadStart &&
+ type.EqualsASCII(sEventStrings[STRING_loadend])) {
+ mMainThreadSeenLoadStart = false;
+
+ RefPtr<LoadStartDetectionRunnable> runnable =
+ new LoadStartDetectionRunnable(this, mXMLHttpRequestPrivate);
+ if (!runnable->RegisterAndDispatch()) {
+ NS_WARNING("Failed to dispatch LoadStartDetectionRunnable!");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(LoadStartDetectionRunnable, Runnable,
+ nsIDOMEventListener)
+
+NS_IMETHODIMP
+LoadStartDetectionRunnable::Run()
+{
+ AssertIsOnMainThread();
+
+ if (NS_FAILED(mXHR->RemoveEventListener(mEventType, this, false))) {
+ NS_WARNING("Failed to remove event listener!");
+ }
+
+ if (!mReceivedLoadStart) {
+ if (mProxy->mOutstandingSendCount > 1) {
+ mProxy->mOutstandingSendCount--;
+ } else if (mProxy->mOutstandingSendCount == 1) {
+ mProxy->Reset();
+
+ RefPtr<ProxyCompleteRunnable> runnable =
+ new ProxyCompleteRunnable(mWorkerPrivate, mProxy,
+ mXMLHttpRequestPrivate, mChannelId);
+ if (runnable->Dispatch()) {
+ mProxy->mWorkerPrivate = nullptr;
+ mProxy->mSyncLoopTarget = nullptr;
+ mProxy->mOutstandingSendCount--;
+ }
+ }
+ }
+
+ mProxy = nullptr;
+ mXHR = nullptr;
+ mXMLHttpRequestPrivate = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadStartDetectionRunnable::HandleEvent(nsIDOMEvent* aEvent)
+{
+ AssertIsOnMainThread();
+
+#ifdef DEBUG
+ {
+ nsString type;
+ if (NS_SUCCEEDED(aEvent->GetType(type))) {
+ MOZ_ASSERT(type == mEventType);
+ }
+ else {
+ NS_WARNING("Failed to get event type!");
+ }
+ }
+#endif
+
+ mReceivedLoadStart = true;
+ return NS_OK;
+}
+
+bool
+EventRunnable::PreDispatch(WorkerPrivate* /* unused */)
+{
+ AssertIsOnMainThread();
+
+ AutoJSAPI jsapi;
+ DebugOnly<bool> ok = jsapi.Init(xpc::NativeGlobal(mScopeObj));
+ MOZ_ASSERT(ok);
+ JSContext* cx = jsapi.cx();
+ // Now keep the mScopeObj alive for the duration
+ JS::Rooted<JSObject*> scopeObj(cx, mScopeObj);
+ // And reset mScopeObj now, before we have a chance to run its destructor on
+ // some background thread.
+ mScopeObj.reset();
+
+ RefPtr<XMLHttpRequestMainThread>& xhr = mProxy->mXHR;
+ MOZ_ASSERT(xhr);
+
+ if (NS_FAILED(xhr->GetResponseType(mResponseType))) {
+ MOZ_ASSERT(false, "This should never fail!");
+ }
+
+ ErrorResult rv;
+ xhr->GetResponseText(mResponseText, rv);
+ mResponseTextResult = rv.StealNSResult();
+
+ if (NS_SUCCEEDED(mResponseTextResult)) {
+ mResponseResult = mResponseTextResult;
+ if (mResponseText.IsVoid()) {
+ mResponse.setNull();
+ }
+ }
+ else {
+ JS::Rooted<JS::Value> response(cx);
+ mResponseResult = xhr->GetResponse(cx, &response);
+ if (NS_SUCCEEDED(mResponseResult)) {
+ if (!response.isGCThing()) {
+ mResponse = response;
+ } else {
+ bool doClone = true;
+ JS::Rooted<JS::Value> transferable(cx);
+ JS::Rooted<JSObject*> obj(cx, response.isObjectOrNull() ?
+ response.toObjectOrNull() : nullptr);
+ if (obj && JS_IsArrayBufferObject(obj)) {
+ // Use cached response if the arraybuffer has been transfered.
+ if (mProxy->mArrayBufferResponseWasTransferred) {
+ MOZ_ASSERT(JS_IsDetachedArrayBufferObject(obj));
+ mUseCachedArrayBufferResponse = true;
+ doClone = false;
+ } else {
+ MOZ_ASSERT(!JS_IsDetachedArrayBufferObject(obj));
+ JS::AutoValueArray<1> argv(cx);
+ argv[0].set(response);
+ obj = JS_NewArrayObject(cx, argv);
+ if (obj) {
+ transferable.setObject(*obj);
+ // Only cache the response when the readyState is DONE.
+ if (xhr->ReadyState() == nsIXMLHttpRequest::DONE) {
+ mProxy->mArrayBufferResponseWasTransferred = true;
+ }
+ } else {
+ mResponseResult = NS_ERROR_OUT_OF_MEMORY;
+ doClone = false;
+ }
+ }
+ }
+
+ if (doClone) {
+ Write(cx, response, transferable, JS::CloneDataPolicy(), rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ NS_WARNING("Failed to clone response!");
+ mResponseResult = rv.StealNSResult();
+ mProxy->mArrayBufferResponseWasTransferred = false;
+ }
+ }
+ }
+ }
+ }
+
+ mStatusResult = xhr->GetStatus(&mStatus);
+
+ xhr->GetStatusText(mStatusText, rv);
+ MOZ_ASSERT(!rv.Failed());
+
+ mReadyState = xhr->ReadyState();
+
+ xhr->GetResponseURL(mResponseURL);
+
+ return true;
+}
+
+bool
+EventRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+{
+ if (mEventStreamId != mProxy->mOuterEventStreamId) {
+ // Threads raced, this event is now obsolete.
+ return true;
+ }
+
+ if (!mProxy->mXMLHttpRequestPrivate) {
+ // Object was finalized, bail.
+ return true;
+ }
+
+ if (mType.EqualsASCII(sEventStrings[STRING_loadstart])) {
+ if (mUploadEvent) {
+ mProxy->mSeenUploadLoadStart = true;
+ }
+ else {
+ mProxy->mSeenLoadStart = true;
+ }
+ }
+ else if (mType.EqualsASCII(sEventStrings[STRING_loadend])) {
+ if (mUploadEvent) {
+ mProxy->mSeenUploadLoadStart = false;
+ }
+ else {
+ if (!mProxy->mSeenLoadStart) {
+ // We've already dispatched premature abort events.
+ return true;
+ }
+ mProxy->mSeenLoadStart = false;
+ }
+ }
+ else if (mType.EqualsASCII(sEventStrings[STRING_abort])) {
+ if ((mUploadEvent && !mProxy->mSeenUploadLoadStart) ||
+ (!mUploadEvent && !mProxy->mSeenLoadStart)) {
+ // We've already dispatched premature abort events.
+ return true;
+ }
+ }
+
+ if (mProgressEvent) {
+ // Cache these for premature abort events.
+ if (mUploadEvent) {
+ mProxy->mLastUploadLengthComputable = mLengthComputable;
+ mProxy->mLastUploadLoaded = mLoaded;
+ mProxy->mLastUploadTotal = mTotal;
+ }
+ else {
+ mProxy->mLastLengthComputable = mLengthComputable;
+ mProxy->mLastLoaded = mLoaded;
+ mProxy->mLastTotal = mTotal;
+ }
+ }
+
+ JS::Rooted<UniquePtr<XMLHttpRequestWorker::StateData>> state(aCx, new XMLHttpRequestWorker::StateData());
+
+ state->mResponseTextResult = mResponseTextResult;
+
+ state->mResponseText = mResponseText;
+
+ if (NS_SUCCEEDED(mResponseTextResult)) {
+ MOZ_ASSERT(mResponse.isUndefined() || mResponse.isNull());
+ state->mResponseResult = mResponseTextResult;
+ state->mResponse = mResponse;
+ }
+ else {
+ state->mResponseResult = mResponseResult;
+
+ if (NS_SUCCEEDED(mResponseResult)) {
+ if (HasData()) {
+ MOZ_ASSERT(mResponse.isUndefined());
+
+ ErrorResult rv;
+ JS::Rooted<JS::Value> response(aCx);
+
+ GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(globalObj.GetAsSupports());
+
+ Read(global, aCx, &response, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ state->mResponse = response;
+ }
+ else {
+ state->mResponse = mResponse;
+ }
+ }
+ }
+
+ state->mStatusResult = mStatusResult;
+ state->mStatus = mStatus;
+
+ state->mStatusText = mStatusText;
+
+ state->mReadyState = mReadyState;
+
+ state->mResponseURL = mResponseURL;
+
+ XMLHttpRequestWorker* xhr = mProxy->mXMLHttpRequestPrivate;
+ xhr->UpdateState(*state.get(), mUseCachedArrayBufferResponse);
+
+ if (mType.EqualsASCII(sEventStrings[STRING_readystatechange])) {
+ if (mReadyState == 4 && !mUploadEvent && !mProxy->mSeenLoadStart) {
+ // We've already dispatched premature abort events.
+ return true;
+ }
+ }
+
+ if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) {
+ return true;
+ }
+
+ JS::Rooted<JSString*> type(aCx,
+ JS_NewUCStringCopyN(aCx, mType.get(), mType.Length()));
+ if (!type) {
+ return false;
+ }
+
+ XMLHttpRequestEventTarget* target;
+ if (mUploadEvent) {
+ target = xhr->GetUploadObjectNoCreate();
+ }
+ else {
+ target = xhr;
+ }
+
+ MOZ_ASSERT(target);
+
+ RefPtr<Event> event;
+ if (mProgressEvent) {
+ ProgressEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mLengthComputable = mLengthComputable;
+ init.mLoaded = mLoaded;
+ init.mTotal = mTotal;
+
+ event = ProgressEvent::Constructor(target, mType, init);
+ }
+ else {
+ event = NS_NewDOMEvent(target, nullptr, nullptr);
+
+ if (event) {
+ event->InitEvent(mType, false, false);
+ }
+ }
+
+ if (!event) {
+ return false;
+ }
+
+ event->SetTrusted(true);
+
+ target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+
+ // After firing the event set mResponse to JSVAL_NULL for chunked response
+ // types.
+ if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) {
+ xhr->NullResponseText();
+ }
+
+ return true;
+}
+
+bool
+WorkerThreadProxySyncRunnable::MainThreadRun()
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIEventTarget> tempTarget = mSyncLoopTarget;
+
+ mProxy->mSyncEventResponseTarget.swap(tempTarget);
+
+ ErrorResult rv;
+ RunOnMainThread(rv);
+ mErrorCode = rv.StealNSResult();
+
+ mProxy->mSyncEventResponseTarget.swap(tempTarget);
+
+ return true;
+}
+
+void
+AbortRunnable::RunOnMainThread(ErrorResult& aRv)
+{
+ mProxy->mInnerEventStreamId++;
+
+ WorkerPrivate* oldWorker = mProxy->mWorkerPrivate;
+ mProxy->mWorkerPrivate = mWorkerPrivate;
+
+ mProxy->mXHR->Abort(aRv);
+
+ mProxy->mWorkerPrivate = oldWorker;
+
+ mProxy->Reset();
+}
+
+nsresult
+OpenRunnable::MainThreadRunInternal()
+{
+ if (!mProxy->Init()) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ nsresult rv;
+
+ if (mBackgroundRequest) {
+ rv = mProxy->mXHR->SetMozBackgroundRequest(mBackgroundRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mWithCredentials) {
+ rv = mProxy->mXHR->SetWithCredentials(mWithCredentials);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mTimeout) {
+ rv = mProxy->mXHR->SetTimeout(mTimeout);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_ASSERT(!mProxy->mInOpen);
+ mProxy->mInOpen = true;
+
+ ErrorResult rv2;
+ mProxy->mXHR->Open(mMethod, mURL, true,
+ mUser.WasPassed() ? mUser.Value() : NullString(),
+ mPassword.WasPassed() ? mPassword.Value() : NullString(),
+ rv2);
+
+ MOZ_ASSERT(mProxy->mInOpen);
+ mProxy->mInOpen = false;
+
+ if (rv2.Failed()) {
+ return rv2.StealNSResult();
+ }
+
+ mProxy->mXHR->SetResponseType(mResponseType, rv2);
+ if (rv2.Failed()) {
+ return rv2.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+void
+SendRunnable::RunOnMainThread(ErrorResult& aRv)
+{
+ nsCOMPtr<nsIVariant> variant;
+
+ if (HasData()) {
+ AutoSafeJSContext cx;
+ JSAutoRequest ar(cx);
+
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc);
+
+ JS::Rooted<JSObject*> globalObject(cx, JS::CurrentGlobalOrNull(cx));
+ if (NS_WARN_IF(!globalObject)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsIGlobalObject> parent = xpc::NativeGlobal(globalObject);
+ if (NS_WARN_IF(!parent)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ JS::Rooted<JS::Value> body(cx);
+ Read(parent, cx, &body, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ aRv = xpc->JSValToVariant(cx, body, getter_AddRefs(variant));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+ else {
+ RefPtr<nsVariant> wvariant = new nsVariant();
+
+ if (NS_FAILED(wvariant->SetAsAString(mStringBody))) {
+ MOZ_ASSERT(false, "This should never fail!");
+ }
+
+ variant = wvariant;
+ }
+
+ // Send() has been already called, reset the proxy.
+ if (mProxy->mWorkerPrivate) {
+ mProxy->Reset();
+ }
+
+ mProxy->mWorkerPrivate = mWorkerPrivate;
+
+ MOZ_ASSERT(!mProxy->mSyncLoopTarget);
+ mProxy->mSyncLoopTarget.swap(mSyncLoopTarget);
+
+ if (mHasUploadListeners) {
+ // Send() can be called more than once before failure,
+ // so don't attach the upload listeners more than once.
+ if (!mProxy->mUploadEventListenersAttached &&
+ !mProxy->AddRemoveEventListeners(true, true)) {
+ MOZ_ASSERT(false, "This should never fail!");
+ }
+ }
+
+ mProxy->mArrayBufferResponseWasTransferred = false;
+
+ mProxy->mInnerChannelId++;
+
+ aRv = mProxy->mXHR->Send(variant);
+
+ if (!aRv.Failed()) {
+ mProxy->mOutstandingSendCount++;
+
+ if (!mHasUploadListeners) {
+ // Send() can be called more than once before failure,
+ // so don't attach the upload listeners more than once.
+ if (!mProxy->mUploadEventListenersAttached &&
+ !mProxy->AddRemoveEventListeners(true, true)) {
+ MOZ_ASSERT(false, "This should never fail!");
+ }
+ }
+ }
+}
+
+XMLHttpRequestWorker::XMLHttpRequestWorker(WorkerPrivate* aWorkerPrivate)
+: mWorkerPrivate(aWorkerPrivate),
+ mResponseType(XMLHttpRequestResponseType::Text), mTimeout(0),
+ mRooted(false), mBackgroundRequest(false), mWithCredentials(false),
+ mCanceled(false), mMozAnon(false), mMozSystem(false)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ mozilla::HoldJSObjects(this);
+}
+
+XMLHttpRequestWorker::~XMLHttpRequestWorker()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ ReleaseProxy(XHRIsGoingAway);
+
+ MOZ_ASSERT(!mRooted);
+
+ mozilla::DropJSObjects(this);
+}
+
+NS_IMPL_ADDREF_INHERITED(XMLHttpRequestWorker, XMLHttpRequestEventTarget)
+NS_IMPL_RELEASE_INHERITED(XMLHttpRequestWorker, XMLHttpRequestEventTarget)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XMLHttpRequestWorker)
+NS_INTERFACE_MAP_END_INHERITING(XMLHttpRequestEventTarget)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(XMLHttpRequestWorker)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XMLHttpRequestWorker,
+ XMLHttpRequestEventTarget)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUpload)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestWorker,
+ XMLHttpRequestEventTarget)
+ tmp->ReleaseProxy(XHRIsGoingAway);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mUpload)
+ tmp->mStateData.mResponse.setUndefined();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(XMLHttpRequestWorker,
+ XMLHttpRequestEventTarget)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mStateData.mResponse)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+/* static */ already_AddRefed<XMLHttpRequest>
+XMLHttpRequestWorker::Construct(const GlobalObject& aGlobal,
+ const MozXMLHttpRequestParameters& aParams,
+ ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+ MOZ_ASSERT(workerPrivate);
+
+ Telemetry::Accumulate(Telemetry::XHR_IN_WORKER, 1);
+
+ RefPtr<XMLHttpRequestWorker> xhr = new XMLHttpRequestWorker(workerPrivate);
+
+ if (workerPrivate->XHRParamsAllowed()) {
+ if (aParams.mMozSystem)
+ xhr->mMozAnon = true;
+ else
+ xhr->mMozAnon = aParams.mMozAnon;
+ xhr->mMozSystem = aParams.mMozSystem;
+ }
+
+ return xhr.forget();
+}
+
+void
+XMLHttpRequestWorker::ReleaseProxy(ReleaseType aType)
+{
+ // Can't assert that we're on the worker thread here because mWorkerPrivate
+ // may be gone.
+
+ if (mProxy) {
+ if (aType == XHRIsGoingAway) {
+ // We're in a GC finalizer, so we can't do a sync call here (and we don't
+ // need to).
+ RefPtr<AsyncTeardownRunnable> runnable =
+ new AsyncTeardownRunnable(mProxy);
+ mProxy = nullptr;
+
+ if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable.forget()))) {
+ NS_ERROR("Failed to dispatch teardown runnable!");
+ }
+ } else {
+ // This isn't necessary if the worker is going away or the XHR is going
+ // away.
+ if (aType == Default) {
+ // Don't let any more events run.
+ mProxy->mOuterEventStreamId++;
+ }
+
+ // We need to make a sync call here.
+ RefPtr<SyncTeardownRunnable> runnable =
+ new SyncTeardownRunnable(mWorkerPrivate, mProxy);
+ mProxy = nullptr;
+
+ ErrorResult forAssertionsOnly;
+ runnable->Dispatch(forAssertionsOnly);
+ if (forAssertionsOnly.Failed()) {
+ NS_ERROR("Failed to dispatch teardown runnable!");
+ }
+ }
+ }
+}
+
+void
+XMLHttpRequestWorker::MaybePin(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mRooted) {
+ return;
+ }
+
+ if (!HoldWorker(mWorkerPrivate, Canceling)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ NS_ADDREF_THIS();
+
+ mRooted = true;
+}
+
+void
+XMLHttpRequestWorker::MaybeDispatchPrematureAbortEvents(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mProxy);
+
+ // Only send readystatechange event when state changed.
+ bool isStateChanged = false;
+ if (mStateData.mReadyState != 4) {
+ isStateChanged = true;
+ mStateData.mReadyState = 4;
+ }
+
+ if (mProxy->mSeenUploadLoadStart) {
+ MOZ_ASSERT(mUpload);
+
+ DispatchPrematureAbortEvent(mUpload, NS_LITERAL_STRING("abort"), true,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ DispatchPrematureAbortEvent(mUpload, NS_LITERAL_STRING("loadend"), true,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ mProxy->mSeenUploadLoadStart = false;
+ }
+
+ if (mProxy->mSeenLoadStart) {
+ if (isStateChanged) {
+ DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("readystatechange"),
+ false, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("abort"), false, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ DispatchPrematureAbortEvent(this, NS_LITERAL_STRING("loadend"), false,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ mProxy->mSeenLoadStart = false;
+ }
+}
+
+void
+XMLHttpRequestWorker::DispatchPrematureAbortEvent(EventTarget* aTarget,
+ const nsAString& aEventType,
+ bool aUploadTarget,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(aTarget);
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<Event> event;
+ if (aEventType.EqualsLiteral("readystatechange")) {
+ event = NS_NewDOMEvent(aTarget, nullptr, nullptr);
+ event->InitEvent(aEventType, false, false);
+ }
+ else {
+ ProgressEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ if (aUploadTarget) {
+ init.mLengthComputable = mProxy->mLastUploadLengthComputable;
+ init.mLoaded = mProxy->mLastUploadLoaded;
+ init.mTotal = mProxy->mLastUploadTotal;
+ }
+ else {
+ init.mLengthComputable = mProxy->mLastLengthComputable;
+ init.mLoaded = mProxy->mLastLoaded;
+ init.mTotal = mProxy->mLastTotal;
+ }
+ event = ProgressEvent::Constructor(aTarget, aEventType, init);
+ }
+
+ if (!event) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ event->SetTrusted(true);
+
+ aTarget->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+}
+
+void
+XMLHttpRequestWorker::Unpin()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ MOZ_ASSERT(mRooted, "Mismatched calls to Unpin!");
+
+ ReleaseWorker();
+
+ mRooted = false;
+
+ NS_RELEASE_THIS();
+}
+
+void
+XMLHttpRequestWorker::SendInternal(SendRunnable* aRunnable,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aRunnable);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ // No send() calls when open is running.
+ if (mProxy->mOpenCount) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ bool hasUploadListeners = mUpload ? mUpload->HasListeners() : false;
+
+ MaybePin(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ AutoUnpinXHR autoUnpin(this);
+ Maybe<AutoSyncLoopHolder> autoSyncLoop;
+
+ nsCOMPtr<nsIEventTarget> syncLoopTarget;
+ bool isSyncXHR = mProxy->mIsSyncXHR;
+ if (isSyncXHR) {
+ autoSyncLoop.emplace(mWorkerPrivate);
+ syncLoopTarget = autoSyncLoop->EventTarget();
+ }
+
+ mProxy->mOuterChannelId++;
+
+ aRunnable->SetSyncLoopTarget(syncLoopTarget);
+ aRunnable->SetHaveUploadListeners(hasUploadListeners);
+
+ aRunnable->Dispatch(aRv);
+ if (aRv.Failed()) {
+ // Dispatch() may have spun the event loop and we may have already unrooted.
+ // If so we don't want autoUnpin to try again.
+ if (!mRooted) {
+ autoUnpin.Clear();
+ }
+ return;
+ }
+
+ if (!isSyncXHR) {
+ autoUnpin.Clear();
+ MOZ_ASSERT(!autoSyncLoop);
+ return;
+ }
+
+ autoUnpin.Clear();
+
+ // Don't clobber an existing exception that we may have thrown on aRv
+ // already... though can there really be one? In any case, it seems to me
+ // that this autoSyncLoop->Run() can never fail, since the StopSyncLoop call
+ // for it will come from ProxyCompleteRunnable and that always passes true for
+ // the second arg.
+ if (!autoSyncLoop->Run() && !aRv.Failed()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+bool
+XMLHttpRequestWorker::Notify(Status aStatus)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (aStatus >= Canceling && !mCanceled) {
+ mCanceled = true;
+ ReleaseProxy(WorkerIsGoingAway);
+ }
+
+ return true;
+}
+
+void
+XMLHttpRequestWorker::Open(const nsACString& aMethod,
+ const nsAString& aUrl, bool aAsync,
+ const Optional<nsAString>& aUser,
+ const Optional<nsAString>& aPassword,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (mProxy) {
+ MaybeDispatchPrematureAbortEvents(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ else {
+ mProxy = new Proxy(this, mMozAnon, mMozSystem);
+ }
+
+ mProxy->mOuterEventStreamId++;
+
+ RefPtr<OpenRunnable> runnable =
+ new OpenRunnable(mWorkerPrivate, mProxy, aMethod, aUrl, aUser, aPassword,
+ mBackgroundRequest, mWithCredentials,
+ mTimeout, mResponseType);
+
+ ++mProxy->mOpenCount;
+ runnable->Dispatch(aRv);
+ if (aRv.Failed()) {
+ if (mProxy && !--mProxy->mOpenCount) {
+ ReleaseProxy();
+ }
+
+ return;
+ }
+
+ // We have been released in one of the nested Open() calls.
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ --mProxy->mOpenCount;
+ mProxy->mIsSyncXHR = !aAsync;
+}
+
+void
+XMLHttpRequestWorker::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<SetRequestHeaderRunnable> runnable =
+ new SetRequestHeaderRunnable(mWorkerPrivate, mProxy, aHeader, aValue);
+ runnable->Dispatch(aRv);
+}
+
+void
+XMLHttpRequestWorker::SetTimeout(uint32_t aTimeout, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ mTimeout = aTimeout;
+
+ if (!mProxy) {
+ // Open may not have been called yet, in which case we'll handle the
+ // timeout in OpenRunnable.
+ return;
+ }
+
+ RefPtr<SetTimeoutRunnable> runnable =
+ new SetTimeoutRunnable(mWorkerPrivate, mProxy, aTimeout);
+ runnable->Dispatch(aRv);
+}
+
+void
+XMLHttpRequestWorker::SetWithCredentials(bool aWithCredentials, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ mWithCredentials = aWithCredentials;
+
+ if (!mProxy) {
+ // Open may not have been called yet, in which case we'll handle the
+ // credentials in OpenRunnable.
+ return;
+ }
+
+ RefPtr<SetWithCredentialsRunnable> runnable =
+ new SetWithCredentialsRunnable(mWorkerPrivate, mProxy, aWithCredentials);
+ runnable->Dispatch(aRv);
+}
+
+void
+XMLHttpRequestWorker::SetMozBackgroundRequest(bool aBackgroundRequest,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ mBackgroundRequest = aBackgroundRequest;
+
+ if (!mProxy) {
+ // Open may not have been called yet, in which case we'll handle the
+ // background request in OpenRunnable.
+ return;
+ }
+
+ RefPtr<SetBackgroundRequestRunnable> runnable =
+ new SetBackgroundRequestRunnable(mWorkerPrivate, mProxy,
+ aBackgroundRequest);
+ runnable->Dispatch(aRv);
+}
+
+XMLHttpRequestUpload*
+XMLHttpRequestWorker::GetUpload(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return nullptr;
+ }
+
+ if (!mUpload) {
+ mUpload = new XMLHttpRequestUpload();
+
+ if (!mUpload) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ return mUpload;
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, NullString());
+
+ // Nothing to clone.
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, const nsAString& aBody,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, aBody);
+
+ // Nothing to clone.
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, JS::Handle<JSObject*> aBody,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aBody);
+
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ JS::Rooted<JS::Value> valToClone(aCx);
+ if (JS_IsArrayBufferObject(aBody) || JS_IsArrayBufferViewObject(aBody)) {
+ valToClone.setObject(*aBody);
+ }
+ else {
+ JS::Rooted<JS::Value> obj(aCx, JS::ObjectValue(*aBody));
+ JSString* bodyStr = JS::ToString(aCx, obj);
+ if (!bodyStr) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ valToClone.setString(bodyStr);
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
+
+ sendRunnable->Write(aCx, valToClone, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, Blob& aBody, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, &aBody, &value)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<BlobImpl> blobImpl = aBody.Impl();
+ MOZ_ASSERT(blobImpl);
+
+ aRv = blobImpl->SetMutable(false);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
+
+ sendRunnable->Write(aCx, value, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, FormData& aBody, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, &aBody, &value)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
+
+ sendRunnable->Write(aCx, value, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, URLSearchParams& aBody,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, &aBody, &value)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<SendRunnable> sendRunnable =
+ new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
+
+ sendRunnable->Write(aCx, value, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ SendInternal(sendRunnable, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, const ArrayBuffer& aBody,
+ ErrorResult& aRv)
+{
+ JS::Rooted<JSObject*> obj(mWorkerPrivate->GetJSContext(), aBody.Obj());
+ return Send(aCx, obj, aRv);
+}
+
+void
+XMLHttpRequestWorker::Send(JSContext* aCx, const ArrayBufferView& aBody,
+ ErrorResult& aRv)
+{
+ if (JS_IsTypedArrayObject(aBody.Obj()) &&
+ JS_GetTypedArraySharedness(aBody.Obj())) {
+ // Throw if the object is mapping shared memory (must opt in).
+ aRv.ThrowTypeError<MSG_TYPEDARRAY_IS_SHARED>(NS_LITERAL_STRING("Argument of XMLHttpRequest.send"));
+ return;
+ }
+ JS::Rooted<JSObject*> obj(aCx, aBody.Obj());
+ return Send(aCx, obj, aRv);
+}
+
+void
+XMLHttpRequestWorker::Abort(ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ return;
+ }
+
+ MaybeDispatchPrematureAbortEvents(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (mStateData.mReadyState == 4) {
+ // No one did anything to us while we fired abort events, so reset our state
+ // to "unsent"
+ mStateData.mReadyState = 0;
+ }
+
+ mProxy->mOuterEventStreamId++;
+
+ RefPtr<AbortRunnable> runnable = new AbortRunnable(mWorkerPrivate, mProxy);
+ runnable->Dispatch(aRv);
+}
+
+void
+XMLHttpRequestWorker::GetResponseHeader(const nsACString& aHeader,
+ nsACString& aResponseHeader, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsCString responseHeader;
+ RefPtr<GetResponseHeaderRunnable> runnable =
+ new GetResponseHeaderRunnable(mWorkerPrivate, mProxy, aHeader,
+ responseHeader);
+ runnable->Dispatch(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ aResponseHeader = responseHeader;
+}
+
+void
+XMLHttpRequestWorker::GetAllResponseHeaders(nsACString& aResponseHeaders,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ if (!mProxy) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsCString responseHeaders;
+ RefPtr<GetAllResponseHeadersRunnable> runnable =
+ new GetAllResponseHeadersRunnable(mWorkerPrivate, mProxy, responseHeaders);
+ runnable->Dispatch(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ aResponseHeaders = responseHeaders;
+}
+
+void
+XMLHttpRequestWorker::OverrideMimeType(const nsAString& aMimeType, ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ // We're supposed to throw if the state is not OPENED or HEADERS_RECEIVED. We
+ // can detect OPENED really easily but we can't detect HEADERS_RECEIVED in a
+ // non-racy way until the XHR state machine actually runs on this thread
+ // (bug 671047). For now we're going to let this work only if the Send()
+ // method has not been called, unless the send has been aborted.
+ if (!mProxy || (SendInProgress() &&
+ (mProxy->mSeenLoadStart ||
+ mStateData.mReadyState > nsIXMLHttpRequest::OPENED))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<OverrideMimeTypeRunnable> runnable =
+ new OverrideMimeTypeRunnable(mWorkerPrivate, mProxy, aMimeType);
+ runnable->Dispatch(aRv);
+}
+
+void
+XMLHttpRequestWorker::SetResponseType(XMLHttpRequestResponseType aResponseType,
+ ErrorResult& aRv)
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mCanceled) {
+ aRv.ThrowUncatchableException();
+ return;
+ }
+
+ // "document" is fine for the main thread but not for a worker. Short-circuit
+ // that here.
+ if (aResponseType == XMLHttpRequestResponseType::Document) {
+ return;
+ }
+
+ if (!mProxy) {
+ // Open() has not been called yet. We store the responseType and we will use
+ // it later in Open().
+ mResponseType = aResponseType;
+ return;
+ }
+
+ if (SendInProgress() &&
+ (mProxy->mSeenLoadStart ||
+ mStateData.mReadyState > nsIXMLHttpRequest::OPENED)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ RefPtr<SetResponseTypeRunnable> runnable =
+ new SetResponseTypeRunnable(mWorkerPrivate, mProxy, aResponseType);
+ runnable->Dispatch(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ mResponseType = runnable->ResponseType();
+}
+
+void
+XMLHttpRequestWorker::GetResponse(JSContext* /* unused */,
+ JS::MutableHandle<JS::Value> aResponse,
+ ErrorResult& aRv)
+{
+ if (NS_SUCCEEDED(mStateData.mResponseTextResult) &&
+ mStateData.mResponse.isUndefined()) {
+ MOZ_ASSERT(NS_SUCCEEDED(mStateData.mResponseResult));
+
+ if (mStateData.mResponseText.IsEmpty()) {
+ mStateData.mResponse =
+ JS_GetEmptyStringValue(mWorkerPrivate->GetJSContext());
+ } else {
+ XMLHttpRequestStringSnapshotReaderHelper helper(mStateData.mResponseText);
+
+ JSString* str =
+ JS_NewUCStringCopyN(mWorkerPrivate->GetJSContext(),
+ helper.Buffer(), helper.Length());
+
+ if (!str) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ mStateData.mResponse.setString(str);
+ }
+ }
+
+ aRv = mStateData.mResponseResult;
+ aResponse.set(mStateData.mResponse);
+}
+
+void
+XMLHttpRequestWorker::GetResponseText(DOMString& aResponseText, ErrorResult& aRv)
+{
+ aRv = mStateData.mResponseTextResult;
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (!mStateData.mResponseText.GetAsString(aResponseText)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+}
+
+void
+XMLHttpRequestWorker::UpdateState(const StateData& aStateData,
+ bool aUseCachedArrayBufferResponse)
+{
+ if (aUseCachedArrayBufferResponse) {
+ MOZ_ASSERT(mStateData.mResponse.isObject() &&
+ JS_IsArrayBufferObject(&mStateData.mResponse.toObject()));
+
+ JS::Rooted<JS::Value> response(mWorkerPrivate->GetJSContext(),
+ mStateData.mResponse);
+ mStateData = aStateData;
+ mStateData.mResponse = response;
+ }
+ else {
+ mStateData = aStateData;
+ }
+
+ XMLHttpRequestBinding::ClearCachedResponseTextValue(this);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/xhr/XMLHttpRequestWorker.h b/dom/xhr/XMLHttpRequestWorker.h
new file mode 100644
index 000000000..90232fe85
--- /dev/null
+++ b/dom/xhr/XMLHttpRequestWorker.h
@@ -0,0 +1,334 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_XMLHttpRequestWorker_h
+#define mozilla_dom_XMLHttpRequestWorker_h
+
+#include "WorkerHolder.h"
+#include "XMLHttpRequest.h"
+#include "XMLHttpRequestString.h"
+#include "mozilla/dom/TypedArray.h"
+
+namespace mozilla {
+namespace dom {
+
+class Proxy;
+class SendRunnable;
+class DOMString;
+
+namespace workers {
+class WorkerPrivate;
+}
+
+class XMLHttpRequestWorker final : public XMLHttpRequest,
+ public workers::WorkerHolder
+{
+public:
+ struct StateData
+ {
+ XMLHttpRequestStringSnapshot mResponseText;
+ nsString mResponseURL;
+ uint32_t mStatus;
+ nsCString mStatusText;
+ uint16_t mReadyState;
+ JS::Heap<JS::Value> mResponse;
+ nsresult mResponseTextResult;
+ nsresult mStatusResult;
+ nsresult mResponseResult;
+
+ StateData()
+ : mStatus(0), mReadyState(0), mResponse(JS::UndefinedValue()),
+ mResponseTextResult(NS_OK), mStatusResult(NS_OK),
+ mResponseResult(NS_OK)
+ { }
+
+ void trace(JSTracer* trc);
+ };
+
+private:
+ RefPtr<XMLHttpRequestUpload> mUpload;
+ workers::WorkerPrivate* mWorkerPrivate;
+ RefPtr<Proxy> mProxy;
+ XMLHttpRequestResponseType mResponseType;
+ StateData mStateData;
+
+ uint32_t mTimeout;
+
+ bool mRooted;
+ bool mBackgroundRequest;
+ bool mWithCredentials;
+ bool mCanceled;
+
+ bool mMozAnon;
+ bool mMozSystem;
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(XMLHttpRequestWorker,
+ XMLHttpRequest)
+
+
+ static already_AddRefed<XMLHttpRequest>
+ Construct(const GlobalObject& aGlobal,
+ const MozXMLHttpRequestParameters& aParams,
+ ErrorResult& aRv);
+
+ void
+ Unpin();
+
+ bool
+ Notify(workers::Status aStatus) override;
+
+ virtual uint16_t
+ ReadyState() const override
+ {
+ return mStateData.mReadyState;
+ }
+
+ virtual void
+ Open(const nsACString& aMethod, const nsAString& aUrl,
+ ErrorResult& aRv) override
+ {
+ Open(aMethod, aUrl, true, Optional<nsAString>(),
+ Optional<nsAString>(), aRv);
+ }
+
+ virtual void
+ Open(const nsACString& aMethod, const nsAString& aUrl, bool aAsync,
+ const nsAString& aUsername, const nsAString& aPassword,
+ ErrorResult& aRv) override
+ {
+ Optional<nsAString> username;
+ username = &aUsername;
+ Optional<nsAString> password;
+ password = &aPassword;
+ Open(aMethod, aUrl, aAsync, username, password, aRv);
+ }
+
+ void
+ Open(const nsACString& aMethod, const nsAString& aUrl,
+ bool aAsync, const Optional<nsAString>& aUser,
+ const Optional<nsAString>& aPassword, ErrorResult& aRv);
+
+ virtual void
+ SetRequestHeader(const nsACString& aHeader, const nsACString& aValue,
+ ErrorResult& aRv) override;
+
+ virtual uint32_t
+ Timeout() const override
+ {
+ return mTimeout;
+ }
+
+ virtual void
+ SetTimeout(uint32_t aTimeout, ErrorResult& aRv) override;
+
+ virtual bool
+ WithCredentials() const override
+ {
+ return mWithCredentials;
+ }
+
+ virtual void
+ SetWithCredentials(bool aWithCredentials, ErrorResult& aRv) override;
+
+ virtual bool
+ MozBackgroundRequest() const override
+ {
+ return mBackgroundRequest;
+ }
+
+ virtual void
+ SetMozBackgroundRequest(bool aBackgroundRequest, ErrorResult& aRv) override;
+
+ virtual nsIChannel*
+ GetChannel() const override
+ {
+ MOZ_CRASH("This method cannot be called on workers.");
+ }
+
+ virtual void
+ GetNetworkInterfaceId(nsACString& aId) const override
+ {
+ MOZ_CRASH("This method cannot be called on workers.");
+ }
+
+ virtual void
+ SetNetworkInterfaceId(const nsACString& aId) override
+ {
+ MOZ_CRASH("This method cannot be called on workers.");
+ }
+
+ virtual XMLHttpRequestUpload*
+ GetUpload(ErrorResult& aRv) override;
+
+ virtual void
+ Send(JSContext* aCx, ErrorResult& aRv) override;
+
+ virtual void
+ Send(JSContext* aCx, const nsAString& aBody, ErrorResult& aRv) override;
+
+ virtual void
+ Send(JSContext* aCx, nsIInputStream* aStream, ErrorResult& aRv) override
+ {
+ MOZ_CRASH("This method cannot be called on workers.");
+ }
+
+ virtual void
+ Send(JSContext* aCx, Blob& aBody, ErrorResult& aRv) override;
+
+ virtual void
+ Send(JSContext* aCx, FormData& aBody, ErrorResult& aRv) override;
+
+ virtual void
+ Send(JSContext* aCx, const ArrayBuffer& aBody, ErrorResult& aRv) override;
+
+ virtual void
+ Send(JSContext* aCx, const ArrayBufferView& aBody, ErrorResult& aRv) override;
+
+ virtual void
+ Send(JSContext* aCx, URLSearchParams& aBody, ErrorResult& aRv) override;
+
+ virtual void
+ Send(JSContext* aCx, nsIDocument& aDoc, ErrorResult& aRv) override
+ {
+ MOZ_CRASH("This method cannot be called on workers.");
+ }
+
+ virtual void
+ Abort(ErrorResult& aRv) override;
+
+ virtual void
+ GetResponseURL(nsAString& aUrl) override
+ {
+ aUrl = mStateData.mResponseURL;
+ }
+
+ uint32_t
+ GetStatus(ErrorResult& aRv) override
+ {
+ aRv = mStateData.mStatusResult;
+ return mStateData.mStatus;
+ }
+
+ virtual void
+ GetStatusText(nsACString& aStatusText, ErrorResult& aRv) override
+ {
+ aStatusText = mStateData.mStatusText;
+ }
+
+ virtual void
+ GetResponseHeader(const nsACString& aHeader, nsACString& aResponseHeader,
+ ErrorResult& aRv) override;
+
+ virtual void
+ GetAllResponseHeaders(nsACString& aResponseHeaders,
+ ErrorResult& aRv) override;
+
+ virtual void
+ OverrideMimeType(const nsAString& aMimeType, ErrorResult& aRv) override;
+
+ virtual XMLHttpRequestResponseType
+ ResponseType() const override
+ {
+ return mResponseType;
+ }
+
+ virtual void
+ SetResponseType(XMLHttpRequestResponseType aResponseType,
+ ErrorResult& aRv) override;
+
+ virtual void
+ GetResponse(JSContext* /* unused */, JS::MutableHandle<JS::Value> aResponse,
+ ErrorResult& aRv) override;
+
+ virtual void
+ GetResponseText(DOMString& aResponseText, ErrorResult& aRv) override;
+
+ virtual nsIDocument*
+ GetResponseXML(ErrorResult& aRv) override
+ {
+ MOZ_CRASH("This method should not be called.");
+ }
+
+ virtual void
+ GetInterface(JSContext* aCx, nsIJSID* aIID,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv) override
+ {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+
+ virtual void
+ SetOriginAttributes(const mozilla::dom::OriginAttributesDictionary& aAttrs) override
+ {
+ MOZ_CRASH("This method cannot be called on workers.");
+ }
+
+ XMLHttpRequestUpload*
+ GetUploadObjectNoCreate() const
+ {
+ return mUpload;
+ }
+
+ void
+ UpdateState(const StateData& aStateData, bool aUseCachedArrayBufferResponse);
+
+ void
+ NullResponseText()
+ {
+ mStateData.mResponseText.SetVoid();
+ mStateData.mResponse.setNull();
+ }
+
+ virtual bool MozAnon() const override
+ {
+ return mMozAnon;
+ }
+
+ virtual bool MozSystem() const override
+ {
+ return mMozSystem;
+ }
+
+ bool
+ SendInProgress() const
+ {
+ return mRooted;
+ }
+
+private:
+ explicit XMLHttpRequestWorker(workers::WorkerPrivate* aWorkerPrivate);
+ ~XMLHttpRequestWorker();
+
+ enum ReleaseType { Default, XHRIsGoingAway, WorkerIsGoingAway };
+
+ void
+ ReleaseProxy(ReleaseType aType = Default);
+
+ void
+ MaybePin(ErrorResult& aRv);
+
+ void
+ MaybeDispatchPrematureAbortEvents(ErrorResult& aRv);
+
+ void
+ DispatchPrematureAbortEvent(EventTarget* aTarget,
+ const nsAString& aEventType, bool aUploadTarget,
+ ErrorResult& aRv);
+
+ void
+ Send(JSContext* aCx, JS::Handle<JSObject*> aBody, ErrorResult& aRv);
+
+ void
+ SendInternal(SendRunnable* aRunnable,
+ ErrorResult& aRv);
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_workers_xmlhttprequest_h__
diff --git a/dom/xhr/moz.build b/dom/xhr/moz.build
new file mode 100644
index 000000000..4ad91deea
--- /dev/null
+++ b/dom/xhr/moz.build
@@ -0,0 +1,40 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIXMLHttpRequest.idl',
+]
+
+XPIDL_MODULE = 'dom_xhr'
+
+EXPORTS.mozilla.dom += [
+ 'XMLHttpRequest.h',
+ 'XMLHttpRequestEventTarget.h',
+ 'XMLHttpRequestMainThread.h',
+ 'XMLHttpRequestString.h',
+ 'XMLHttpRequestUpload.h',
+]
+
+UNIFIED_SOURCES += [
+ 'XMLHttpRequest.cpp',
+ 'XMLHttpRequestEventTarget.cpp',
+ 'XMLHttpRequestMainThread.cpp',
+ 'XMLHttpRequestString.cpp',
+ 'XMLHttpRequestUpload.cpp',
+ 'XMLHttpRequestWorker.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/dom/workers',
+ '/netwerk/base',
+]
+
+MOCHITEST_MANIFESTS += [ 'tests/mochitest.ini' ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/xhr/nsIXMLHttpRequest.idl b/dom/xhr/nsIXMLHttpRequest.idl
new file mode 100644
index 000000000..53e80bab7
--- /dev/null
+++ b/dom/xhr/nsIXMLHttpRequest.idl
@@ -0,0 +1,349 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIDOMEventTarget.idl"
+
+interface nsIChannel;
+interface nsIDOMDocument;
+interface nsIDOMEventListener;
+interface nsILoadGroup;
+interface nsIPrincipal;
+interface nsIScriptContext;
+interface nsIURI;
+interface nsIVariant;
+interface nsIGlobalObject;
+interface nsIInputStream;
+interface nsIDOMBlob;
+
+[builtinclass, uuid(88e7d2a0-2e5b-4f65-9624-a61e607a9948)]
+interface nsIXMLHttpRequestEventTarget : nsIDOMEventTarget {
+ // event handler attributes
+};
+
+[builtinclass, uuid(d74c4dc4-bc8c-4f5d-b7f1-121a48750abe)]
+interface nsIXMLHttpRequestUpload : nsIXMLHttpRequestEventTarget {
+ // for future use
+};
+
+/**
+ * Mozilla's XMLHttpRequest is modelled after Microsoft's IXMLHttpRequest
+ * object. The goal has been to make Mozilla's version match Microsoft's
+ * version as closely as possible, but there are bound to be some differences.
+ *
+ * In general, Microsoft's documentation for IXMLHttpRequest can be used.
+ * Mozilla's interface definitions provide some additional documentation. The
+ * web page to look at is http://www.mozilla.org/xmlextras/
+ *
+ * Mozilla's XMLHttpRequest object can be created in JavaScript like this:
+ * new XMLHttpRequest()
+ * compare to Internet Explorer:
+ * new ActiveXObject("Msxml2.XMLHTTP")
+ *
+ * From JavaScript, the methods and properties visible in the XMLHttpRequest
+ * object are a combination of nsIXMLHttpRequest and nsIJSXMLHttpRequest;
+ * there is no need to differentiate between those interfaces.
+ *
+ * From native code, the way to set up onload and onerror handlers is a bit
+ * different. Here is a comment from Johnny Stenback <jst@netscape.com>:
+ *
+ * The mozilla implementation of nsIXMLHttpRequest implements the interface
+ * nsIDOMEventTarget and that's how you're supported to add event listeners.
+ * Try something like this:
+ *
+ * nsCOMPtr<nsIDOMEventTarget> target(do_QueryInterface(myxmlhttpreq));
+ *
+ * target->AddEventListener(NS_LITERAL_STRING("load"), mylistener,
+ * PR_FALSE)
+ *
+ * where mylistener is your event listener object that implements the
+ * interface nsIDOMEventListener.
+ *
+ * The 'onload', 'onerror', and 'onreadystatechange' attributes moved to
+ * nsIJSXMLHttpRequest, but if you're coding in C++ you should avoid using
+ * those.
+ *
+ * Conclusion: Do not use event listeners on XMLHttpRequest from C++, unless
+ * you're aware of all the security implications. And then think twice about
+ * it.
+ */
+[scriptable, uuid(6f54214c-7175-498d-9d2d-0429e38c2869)]
+interface nsIXMLHttpRequest : nsISupports
+{
+ /**
+ * The request uses a channel in order to perform the
+ * request. This attribute represents the channel used
+ * for the request. NULL if the channel has not yet been
+ * created.
+ *
+ * Mozilla only. Requires elevated privileges to access.
+ */
+ readonly attribute nsIChannel channel;
+
+ /**
+ * The response to the request is parsed as if it were a
+ * text/xml stream. This attributes represents the response as
+ * a DOM Document object. NULL if the request is unsuccessful or
+ * has not yet been sent.
+ */
+ readonly attribute nsIDOMDocument responseXML;
+
+ /**
+ * The response to the request as text.
+ * NULL if the request is unsuccessful or
+ * has not yet been sent.
+ */
+ readonly attribute AString responseText;
+
+ /**
+ * Determine a response format which response attribute returns.
+ * empty string (initial value) or "text": as text.
+ * "arraybuffer": as a typed array ArrayBuffer.
+ * "blob": as a File API Blob.
+ * "document": as a DOM Document object.
+ */
+ attribute AString responseType;
+
+ /**
+ * The response to the request as a specified format by responseType.
+ * NULL if the request is unsuccessful or
+ * has not yet been sent.
+ */
+ [implicit_jscontext] readonly attribute jsval /* any */ response;
+
+ /**
+ * The status of the response to the request for HTTP requests.
+ */
+ // XXX spec says unsigned short
+ readonly attribute unsigned long status;
+
+ /**
+ * The string representing the status of the response for
+ * HTTP requests.
+ */
+ readonly attribute ACString statusText;
+
+ /**
+ * If the request has been sent already, this method will
+ * abort the request.
+ */
+ [binaryname(SlowAbort)] void abort();
+
+ /**
+ * Returns all of the response headers as a string for HTTP
+ * requests.
+ *
+ * @returns A string containing all of the response headers.
+ * The empty string if the response has not yet been received.
+ */
+ ACString getAllResponseHeaders();
+
+ /**
+ * Returns the text of the header with the specified name for
+ * HTTP requests.
+ *
+ * @param header The name of the header to retrieve
+ * @returns A string containing the text of the header specified.
+ * NULL if the response has not yet been received or the
+ * header does not exist in the response.
+ */
+ ACString getResponseHeader(in ACString header);
+
+%{C++
+ // note this is NOT virtual so this won't muck with the vtable!
+ inline nsresult Open(const nsACString& method, const nsACString& url,
+ bool async, const nsAString& user,
+ const nsAString& password) {
+ return Open(method, url, async, user, password, 3);
+ }
+%}
+ /**
+ * Meant to be a script-only method for initializing a request.
+ *
+ * If there is an "active" request (that is, if open() has been called
+ * already), this is equivalent to calling abort() and then open().
+ *
+ * @param method The HTTP method - either "POST" or "GET". Ignored
+ * if the URL is not a HTTP URL.
+ * @param url The URL to which to send the request.
+ * @param async (optional) Whether the request is synchronous or
+ * asynchronous i.e. whether send returns only after
+ * the response is received or if it returns immediately after
+ * sending the request. In the latter case, notification
+ * of completion is sent through the event listeners.
+ * The default value is true.
+ * @param user (optional) A username for authentication if necessary.
+ * The default value is the empty string
+ * @param password (optional) A password for authentication if necessary.
+ * The default value is the empty string
+ */
+ [optional_argc] void open(in ACString method, in AUTF8String url,
+ [optional] in boolean async,
+ [optional,Undefined(Empty)] in DOMString user,
+ [optional,Undefined(Empty)] in DOMString password);
+
+ /**
+ * Sends the request. If the request is asynchronous, returns
+ * immediately after sending the request. If it is synchronous
+ * returns only after the response has been received.
+ *
+ * All event listeners must be set before calling send().
+ *
+ * After the initial response, all event listeners will be cleared.
+ * // XXXbz what does that mean, exactly?
+ *
+ * @param body Either an instance of nsIDOMDocument, nsIInputStream
+ * or a string (nsISupportsString in the native calling
+ * case). This is used to populate the body of the
+ * HTTP request if the HTTP request method is "POST".
+ * If the parameter is a nsIDOMDocument, it is serialized.
+ * If the parameter is a nsIInputStream, then it must be
+ * compatible with nsIUploadChannel.setUploadStream, and a
+ * Content-Length header will be added to the HTTP request
+ * with a value given by nsIInputStream.available. Any
+ * headers included at the top of the stream will be
+ * treated as part of the message body. The MIME type of
+ * the stream should be specified by setting the Content-
+ * Type header via the setRequestHeader method before
+ * calling send.
+ */
+ void send([optional] in nsIVariant body);
+
+ /**
+ * Sets a HTTP request header for HTTP requests. You must call open
+ * before setting the request headers.
+ *
+ * @param header The name of the header to set in the request.
+ * @param value The body of the header.
+ */
+ void setRequestHeader(in ACString header, in ACString value);
+
+ /**
+ * The amount of milliseconds a request can take before being terminated.
+ * Initially zero. Zero means there is no timeout.
+ */
+ attribute unsigned long timeout;
+
+ /**
+ * The state of the request.
+ *
+ * Possible values:
+ * 0 UNSENT open() has not been called yet.
+ * 1 OPENED send() has not been called yet.
+ * 2 HEADERS_RECEIVED
+ * send() has been called, headers and status are available.
+ * 3 LOADING Downloading, responseText holds the partial data.
+ * 4 DONE Finished with all operations.
+ */
+ const unsigned short UNSENT = 0;
+ const unsigned short OPENED = 1;
+ const unsigned short HEADERS_RECEIVED = 2;
+ const unsigned short LOADING = 3;
+ const unsigned short DONE = 4;
+ readonly attribute unsigned short readyState;
+
+ /**
+ * Override the mime type returned by the server (if any). This may
+ * be used, for example, to force a stream to be treated and parsed
+ * as text/xml, even if the server does not report it as such. This
+ * must be done before the <code>send</code> method is invoked.
+ *
+ * @param mimetype The type used to override that returned by the server
+ * (if any).
+ */
+ [binaryname(SlowOverrideMimeType)] void overrideMimeType(in DOMString mimetype);
+
+ /**
+ * Set to true if this is a background service request. This will
+ * prevent a load group being associated with the request, and
+ * suppress any security dialogs from being shown * to the user.
+ * In the cases where one of those dialogs would be shown, the request
+ * will simply fail instead.
+ */
+ attribute boolean mozBackgroundRequest;
+
+ /**
+ * When set to true attempts to make cross-site Access-Control requests
+ * with credentials such as cookies and authorization headers.
+ *
+ * Never affects same-site requests.
+ *
+ * Defaults to false.
+ */
+ attribute boolean withCredentials;
+
+ /**
+ * Initialize the object for use from C++ code with the principal, script
+ * context, and owner window that should be used.
+ *
+ * @param principal The principal to use for the request. This must not be
+ * null.
+ * @param globalObject The associated global for the request. Can be the
+ * outer window, a sandbox, or a backstage pass.
+ * May be null, but then the request cannot create a
+ * document.
+ * @param baseURI The base URI to use when resolving relative URIs. May be
+ * null.
+ * @param loadGroup An optional load group to use when performing the request.
+ * This will be used even if the global has a window with a
+ * load group.
+ */
+ [noscript] void init(in nsIPrincipal principal,
+ in nsIGlobalObject globalObject,
+ in nsIURI baseURI,
+ [optional] in nsILoadGroup loadGroup);
+
+ /**
+ * Upload process can be tracked by adding event listener to |upload|.
+ */
+ readonly attribute nsIXMLHttpRequestUpload upload;
+
+ /**
+ * Meant to be a script-only mechanism for setting a callback function.
+ * The attribute is expected to be JavaScript function object. When the
+ * readyState changes, the callback function will be called.
+ * This attribute should not be used from native code!!
+ *
+ * After the initial response, all event listeners will be cleared.
+ * // XXXbz what does that mean, exactly?
+ *
+ * Call open() before setting an onreadystatechange listener.
+ */
+ [implicit_jscontext] attribute jsval onreadystatechange;
+
+ /**
+ * If true, the request will be sent without cookie and authentication
+ * headers.
+ */
+ readonly attribute boolean mozAnon;
+
+ /**
+ * If true, the same origin policy will not be enforced on the request.
+ */
+ readonly attribute boolean mozSystem;
+};
+
+[uuid(840d0d00-e83e-4a29-b3c7-67e96e90a499)]
+interface nsIXHRSendable : nsISupports {
+ void getSendInfo(out nsIInputStream body,
+ out uint64_t contentLength,
+ out ACString contentType,
+ out ACString charset);
+};
+
+/**
+ * @deprecated
+ */
+[scriptable, uuid(8ae70a39-edf1-40b4-a992-472d23421c25)]
+interface nsIJSXMLHttpRequest : nsISupports {
+};
+
+%{ C++
+#define NS_XMLHTTPREQUEST_CID \
+ { /* d164e770-4157-11d4-9a42-000064657374 */ \
+ 0xd164e770, 0x4157, 0x11d4, \
+ {0x9a, 0x42, 0x00, 0x00, 0x64, 0x65, 0x73, 0x74} }
+#define NS_XMLHTTPREQUEST_CONTRACTID \
+"@mozilla.org/xmlextras/xmlhttprequest;1"
+%}
diff --git a/dom/xhr/tests/browser.ini b/dom/xhr/tests/browser.ini
new file mode 100644
index 000000000..6bf9a3fc8
--- /dev/null
+++ b/dom/xhr/tests/browser.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ browser_xhr_onchange_leak.html
+[browser_blobFromFile.js]
+[browser_xhr_onchange_leak.js] \ No newline at end of file
diff --git a/dom/xhr/tests/browser_xhr_onchange_leak.html b/dom/xhr/tests/browser_xhr_onchange_leak.html
new file mode 100644
index 000000000..56eb45533
--- /dev/null
+++ b/dom/xhr/tests/browser_xhr_onchange_leak.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test page for https://bugzilla.mozilla.org/show_bug.cgi?id=1336811
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test page for Bug 1336811</title>
+</head>
+<body onload="onload();">
+<p><span id="samplepage">sample page</span></p>
+<script type="application/javascript">
+ function onload() {
+ var request = new XMLHttpRequest;
+ request.open("GET", "about:blank");
+ request.onreadystatechange = function() {
+ request.foo = request;
+ }
+ request.foo = request;
+ request.send();
+ request.foo = request;
+ }
+</script>
+</body>
+</html>
diff --git a/dom/xhr/tests/browser_xhr_onchange_leak.js b/dom/xhr/tests/browser_xhr_onchange_leak.js
new file mode 100644
index 000000000..f4dbb7e3e
--- /dev/null
+++ b/dom/xhr/tests/browser_xhr_onchange_leak.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// Bug 1336811 - An XHR that has a .onreadystatechange waiting should
+// not leak forever once the tab is closed. CC optimizations need to be
+// turned off once it is closed.
+
+add_task(function* test() {
+ const url = "http://mochi.test:8888/browser/dom/xhr/tests/browser_xhr_onchange_leak.html";
+ let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ let browser = gBrowser.selectedBrowser;
+ let done = yield ContentTask.spawn(browser,{}, function*(browser){
+ let doc = content.document;
+ let promise = ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true);
+ content.location = "about:home";
+ yield promise;
+ return true;
+ });
+ is(done, true, "need to check something");
+ yield BrowserTestUtils.removeTab(newTab);
+});
diff --git a/dom/xhr/tests/common_temporaryFileBlob.js b/dom/xhr/tests/common_temporaryFileBlob.js
new file mode 100644
index 000000000..81a66238d
--- /dev/null
+++ b/dom/xhr/tests/common_temporaryFileBlob.js
@@ -0,0 +1,100 @@
+var data = new Array(256).join("1234567890ABCDEF");
+
+function createXHR() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", "temporaryFileBlob.sjs");
+ xhr.responseType = 'blob';
+ xhr.send({toString: function() { return data; }});
+ return xhr;
+}
+
+function test_simple() {
+ info("Simple test");
+
+ var xhr = createXHR();
+
+ xhr.onloadend = function() {
+ ok(xhr.response instanceof Blob, "We have a blob!");
+ is(xhr.response.size, data.length, "Data length matches");
+
+ var fr = new FileReader();
+ fr.readAsText(xhr.response);
+ fr.onload = function() {
+ is(fr.result, data, "Data content matches");
+ next();
+ }
+ }
+}
+
+function test_abort() {
+ info("Aborting during onloading");
+
+ var xhr = createXHR();
+
+ xhr.onprogress = function() {
+ xhr.abort();
+ }
+
+ xhr.onloadend = function() {
+ ok(!xhr.response, "We should not have a Blob!");
+ next();
+ }
+}
+
+function test_reuse() {
+ info("Reuse test");
+
+ var xhr = createXHR();
+
+ var count = 0;
+ xhr.onloadend = function() {
+ ok(xhr.response instanceof Blob, "We have a blob!");
+ is(xhr.response.size, data.length, "Data length matches");
+
+ var fr = new FileReader();
+ fr.readAsText(xhr.response);
+ fr.onload = function() {
+ is(fr.result, data, "Data content matches");
+ if (++count > 2) {
+ next();
+ return;
+ }
+
+ xhr.open("POST", "temporaryFileBlob.sjs");
+ xhr.responseType = 'blob';
+ xhr.send({toString: function() { return data; }});
+ }
+ }
+}
+
+function test_worker_generic(test) {
+ var w = new Worker('worker_temporaryFileBlob.js');
+ w.onmessage = function(e) {
+ if (e.data.type == 'info') {
+ info(e.data.msg);
+ } else if (e.data.type == 'check') {
+ ok(e.data.what, e.data.msg);
+ } else if (e.data.type == 'finish') {
+ next();
+ } else {
+ ok(false, 'Something wrong happened');
+ }
+ }
+
+ w.postMessage(test);
+}
+
+function test_worker() {
+ info("XHR in workers");
+ test_worker_generic('simple');
+}
+
+function test_worker_abort() {
+ info("XHR in workers");
+ test_worker_generic('abort');
+}
+
+function test_worker_reuse() {
+ info("XHR in workers");
+ test_worker_generic('reuse');
+}
diff --git a/dom/xhr/tests/echo.sjs b/dom/xhr/tests/echo.sjs
new file mode 100644
index 000000000..6e3242c9f
--- /dev/null
+++ b/dom/xhr/tests/echo.sjs
@@ -0,0 +1,21 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response)
+{
+ response.setHeader("Content-Type", "text/plain");
+ if (request.method == "GET") {
+ response.write(request.queryString);
+ return;
+ }
+
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+ var body = "";
+ var bodyAvail;
+ while ((bodyAvail = bodyStream.available()) > 0)
+ body += String.fromCharCode.apply(null, bodyStream.readByteArray(bodyAvail));
+
+ response.write(body);
+}
diff --git a/dom/xhr/tests/empty.html b/dom/xhr/tests/empty.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/dom/xhr/tests/empty.html
diff --git a/dom/xhr/tests/file_XHRDocURI.html b/dom/xhr/tests/file_XHRDocURI.html
new file mode 100644
index 000000000..8dbbb0549
--- /dev/null
+++ b/dom/xhr/tests/file_XHRDocURI.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>XMLHttpRequest return document URIs</title>
+ </head>
+ <body>
+ <div id="data">data</div>
+ </body>
+</html>
diff --git a/dom/xhr/tests/file_XHRDocURI.html^headers^ b/dom/xhr/tests/file_XHRDocURI.html^headers^
new file mode 100644
index 000000000..ddfd8c0a9
--- /dev/null
+++ b/dom/xhr/tests/file_XHRDocURI.html^headers^
@@ -0,0 +1,3 @@
+HTTP 200 OK
+Access-Control-Allow-Origin: *
+Content-Type: text/html
diff --git a/dom/xhr/tests/file_XHRDocURI.sjs b/dom/xhr/tests/file_XHRDocURI.sjs
new file mode 100644
index 000000000..275b1e96e
--- /dev/null
+++ b/dom/xhr/tests/file_XHRDocURI.sjs
@@ -0,0 +1,13 @@
+function handleRequest(aRequest, aResponse)
+{
+ var url = aRequest.queryString.match(/\burl=([^#&]*)/);
+ if (!url) {
+ aResponse.setStatusLine(aRequest.httpVersion, 404, "Not Found");
+ return;
+ }
+
+ url = decodeURIComponent(url[1]);
+ aResponse.setStatusLine(aRequest.httpVersion, 302, "Found");
+ aResponse.setHeader("Cache-Control", "no-cache", false);
+ aResponse.setHeader("Location", url, false);
+}
diff --git a/dom/xhr/tests/file_XHRDocURI.text b/dom/xhr/tests/file_XHRDocURI.text
new file mode 100644
index 000000000..c1dead8d7
--- /dev/null
+++ b/dom/xhr/tests/file_XHRDocURI.text
@@ -0,0 +1 @@
+<res>data</res>
diff --git a/dom/xhr/tests/file_XHRDocURI.text^headers^ b/dom/xhr/tests/file_XHRDocURI.text^headers^
new file mode 100644
index 000000000..b2fe7cdeb
--- /dev/null
+++ b/dom/xhr/tests/file_XHRDocURI.text^headers^
@@ -0,0 +1,3 @@
+HTTP 200 OK
+Access-Control-Allow-Origin: *
+Content-Type: text/plain
diff --git a/dom/xhr/tests/file_XHRDocURI.xml b/dom/xhr/tests/file_XHRDocURI.xml
new file mode 100644
index 000000000..c1a9a5256
--- /dev/null
+++ b/dom/xhr/tests/file_XHRDocURI.xml
@@ -0,0 +1 @@
+<res xml:base="http://www.example.com/">data</res>
diff --git a/dom/xhr/tests/file_XHRDocURI.xml^headers^ b/dom/xhr/tests/file_XHRDocURI.xml^headers^
new file mode 100644
index 000000000..f47804337
--- /dev/null
+++ b/dom/xhr/tests/file_XHRDocURI.xml^headers^
@@ -0,0 +1,3 @@
+HTTP 200 OK
+Access-Control-Allow-Origin: *
+Content-Type: application/xml
diff --git a/dom/xhr/tests/file_XHRResponseURL.js b/dom/xhr/tests/file_XHRResponseURL.js
new file mode 100644
index 000000000..7c48201b2
--- /dev/null
+++ b/dom/xhr/tests/file_XHRResponseURL.js
@@ -0,0 +1,303 @@
+"use strict";
+
+// utility functions for worker/window communication
+
+function isInWorker() {
+ try {
+ return !(self instanceof Window);
+ } catch (e) {
+ return true;
+ }
+}
+
+function message(aData) {
+ if (isInWorker()) {
+ self.postMessage(aData);
+ } else {
+ self.postMessage(aData, "*");
+ }
+}
+message.ping = 0;
+message.pong = 0;
+
+function is(aActual, aExpected, aMessage) {
+ var obj = {
+ type: "is",
+ actual: aActual,
+ expected: aExpected,
+ message: aMessage
+ };
+ ++message.ping;
+ message(obj);
+}
+
+function ok(aBool, aMessage) {
+ var obj = {
+ type: "ok",
+ bool: aBool,
+ message: aMessage
+ };
+ ++message.ping;
+ message(obj);
+}
+
+function info(aMessage) {
+ var obj = {
+ type: "info",
+ message: aMessage
+ };
+ ++message.ping;
+ message(obj);
+}
+
+function request(aURL) {
+ return new Promise(function (aResolve, aReject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aURL);
+ xhr.addEventListener("load", function () {
+ xhr.succeeded = true;
+ aResolve(xhr);
+ });
+ xhr.addEventListener("error", function () {
+ xhr.succeeded = false;
+ aResolve(xhr);
+ });
+ xhr.send();
+ });
+}
+
+function createSequentialRequest(aParameters, aTest) {
+ var sequence = aParameters.reduce(function (aPromise, aParam) {
+ return aPromise.then(function () {
+ return request(aParam.requestURL);
+ }).then(function (aXHR) {
+ return aTest(aXHR, aParam);
+ });
+ }, Promise.resolve());
+
+ return sequence;
+}
+
+function testSuccessResponse() {
+ var blob = new Blob(["data"], {type: "text/plain"});
+ var blobURL = URL.createObjectURL(blob);
+
+ var parameters = [
+ // tests that start with same-origin request
+ {
+ message: "request to same-origin without redirect",
+ requestURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ responseURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text"
+ },
+ {
+ message: "request to same-origin redirect to same-origin URL",
+ requestURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ responseURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text"
+ },
+ {
+ message: "request to same-origin redirects several times and finally go to same-origin URL",
+ requestURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text"),
+ responseURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text"
+ },
+ {
+ message: "request to same-origin redirect to cross-origin URL",
+ requestURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ responseURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ },
+ {
+ message: "request to same-origin redirects several times and finally go to cross-origin URL",
+ requestURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.text"),
+ responseURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ },
+
+ // tests that start with cross-origin request
+ {
+ message: "request to cross-origin without redirect",
+ requestURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ responseURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.text"
+ },
+ {
+ message: "request to cross-origin redirect back to same-origin URL",
+ requestURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ responseURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text"
+ },
+ {
+ message: "request to cross-origin redirect to the same cross-origin URL",
+ requestURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ responseURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ },
+ {
+ message: "request to cross-origin redirect to another cross-origin URL",
+ requestURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://example.org/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ responseURL: "http://example.org/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ },
+ {
+ message: "request to cross-origin redirects several times and finally go to same-origin URL",
+ requestURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text"),
+ responseURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ },
+ {
+ message: "request to cross-origin redirects several times and finally go to the same cross-origin URL",
+ requestURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.text"),
+ responseURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ },
+ {
+ message: "request to cross-origin redirects several times and finally go to another cross-origin URL",
+ requestURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://example.org/tests/dom/xhr/tests/file_XHRResponseURL.text"),
+ responseURL: "http://example.org/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ },
+ {
+ message: "request to cross-origin redirects to another cross-origin and finally go to the other cross-origin URL",
+ requestURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.org/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://test1.example.com/tests/dom/xhr/tests/file_XHRResponseURL.text"),
+ responseURL: "http://test1.example.com/tests/dom/xhr/tests/file_XHRResponseURL.text",
+ },
+ {
+ message: "request URL has fragment",
+ requestURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text#fragment",
+ responseURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text"
+ },
+
+ // tests for non-http(s) URL
+ {
+ message: "request to data: URL",
+ requestURL: "data:text/plain,data",
+ responseURL: "data:text/plain,data"
+ },
+ {
+ message: "request to blob: URL",
+ requestURL: blobURL,
+ responseURL: blobURL
+ }
+ ];
+
+ var sequence = createSequentialRequest(parameters, function (aXHR, aParam) {
+ ok(aXHR.succeeded, "assert request succeeded");
+ is(aXHR.responseURL, aParam.responseURL, aParam.message);
+ });
+
+ sequence.then(function () {
+ URL.revokeObjectURL(blobURL);
+ });
+
+ return sequence;
+}
+
+function testFailedResponse() {
+ info("test not to leak responseURL for denied cross-origin request");
+ var parameters = [
+ {
+ message: "should be empty for denied cross-origin request without redirect",
+ requestURL: "http://example.com/tests/dom/xhr/tests/file_XHRResponseURL_nocors.text"
+ },
+ {
+ message: "should be empty for denied cross-origin request with redirect",
+ requestURL: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRResponseURL_nocors.text"
+ }
+ ];
+
+ var sequence = createSequentialRequest(parameters, function (aXHR, aParam) {
+ ok(!aXHR.succeeded, "assert request failed");
+ is(aXHR.responseURL, "" , aParam.message);
+ });
+
+ return sequence;
+}
+
+function testNotToLeakResponseURLWhileDoingRedirects() {
+ info("test not to leak responeseURL while doing redirects");
+
+ if (isInWorker()) {
+ return testNotToLeakResponseURLWhileDoingRedirectsInWorker();
+ } else {
+ return testNotToLeakResponseURLWhileDoingRedirectsInWindow();
+ }
+}
+
+function testNotToLeakResponseURLWhileDoingRedirectsInWindow() {
+ var xhr = new XMLHttpRequest();
+ var requestObserver = {
+ observe: function (aSubject, aTopic, aData) {
+ is(xhr.readyState, XMLHttpRequest.OPENED, "assert for XHR state");
+ is(xhr.responseURL, "",
+ "responseURL should return empty string before HEADERS_RECEIVED");
+ }
+ };
+ SpecialPowers.addObserver(requestObserver, "specialpowers-http-notify-request", false);
+
+ return new Promise(function (aResolve, aReject) {
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text");
+ xhr.addEventListener("load", function () {
+ SpecialPowers.removeObserver(requestObserver, "specialpowers-http-notify-request");
+ aResolve();
+ });
+ xhr.addEventListener("error", function () {
+ ok(false, "unexpected request falilure");
+ SpecialPowers.removeObserver(requestObserver, "specialpowers-http-notify-request");
+ aResolve();
+ });
+ xhr.send();
+ });
+}
+
+function testNotToLeakResponseURLWhileDoingRedirectsInWorker() {
+ var xhr = new XMLHttpRequest();
+ var testRedirect = function (e) {
+ if (e.data === "request" && xhr.readyState === XMLHttpRequest.OPENED) {
+ is(xhr.responseURL, "",
+ "responseURL should return empty string before HEADERS_RECEIVED");
+ }
+ };
+
+ return new Promise(function (aResolve, aReject) {
+ self.addEventListener("message", testRedirect);
+ message({type: "redirect_test", status: "start"});
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.sjs?url=http://mochi.test:8888/tests/dom/xhr/tests/file_XHRResponseURL.text");
+ xhr.addEventListener("load", function () {
+ self.removeEventListener("message", testRedirect);
+ message({type: "redirect_test", status: "end"});
+ aResolve();
+ });
+ xhr.addEventListener("error", function (e) {
+ ok(false, "unexpected request falilure");
+ self.removeEventListener("message", testRedirect);
+ message({type: "redirect_test", status: "end"});
+ aResolve();
+ });
+ xhr.send();
+ });
+}
+
+function waitForAllMessagesProcessed() {
+ return new Promise(function (aResolve, aReject) {
+ var id = setInterval(function () {
+ if (message.ping === message.pong) {
+ clearInterval(id);
+ aResolve();
+ }
+ }, 100);
+ });
+}
+
+
+self.addEventListener("message", function (aEvent) {
+ if (aEvent.data === "start") {
+ ok("responseURL" in (new XMLHttpRequest()),
+ "XMLHttpRequest should have responseURL attribute");
+ is((new XMLHttpRequest()).responseURL, "",
+ "responseURL should be empty string if response's url is null");
+
+ var promise = testSuccessResponse();
+ promise.then(function () {
+ return testFailedResponse();
+ }).then(function () {
+ return testNotToLeakResponseURLWhileDoingRedirects();
+ }).then(function () {
+ return waitForAllMessagesProcessed();
+ }).then(function () {
+ message("done");
+ });
+ }
+ if (aEvent.data === "pong") {
+ ++message.pong;
+ }
+});
diff --git a/dom/xhr/tests/file_XHRResponseURL.sjs b/dom/xhr/tests/file_XHRResponseURL.sjs
new file mode 100644
index 000000000..5ac0a346c
--- /dev/null
+++ b/dom/xhr/tests/file_XHRResponseURL.sjs
@@ -0,0 +1,14 @@
+function handleRequest(aRequest, aResponse)
+{
+ var url = aRequest.queryString.match(/\burl=([^#&]*)/);
+ if (!url) {
+ aResponse.setStatusLine(aRequest.httpVersion, 404, "Not Found");
+ return;
+ }
+ url = decodeURIComponent(url[1]);
+
+ aResponse.setStatusLine(aRequest.httpVersion, 302, "Found");
+ aResponse.setHeader("Access-Control-Allow-Origin", "*", false);
+ aResponse.setHeader("Cache-Control", "no-cache", false);
+ aResponse.setHeader("Location", url, false);
+}
diff --git a/dom/xhr/tests/file_XHRResponseURL.text b/dom/xhr/tests/file_XHRResponseURL.text
new file mode 100644
index 000000000..1269488f7
--- /dev/null
+++ b/dom/xhr/tests/file_XHRResponseURL.text
@@ -0,0 +1 @@
+data
diff --git a/dom/xhr/tests/file_XHRResponseURL.text^headers^ b/dom/xhr/tests/file_XHRResponseURL.text^headers^
new file mode 100644
index 000000000..b2fe7cdeb
--- /dev/null
+++ b/dom/xhr/tests/file_XHRResponseURL.text^headers^
@@ -0,0 +1,3 @@
+HTTP 200 OK
+Access-Control-Allow-Origin: *
+Content-Type: text/plain
diff --git a/dom/xhr/tests/file_XHRResponseURL_nocors.text b/dom/xhr/tests/file_XHRResponseURL_nocors.text
new file mode 100644
index 000000000..1269488f7
--- /dev/null
+++ b/dom/xhr/tests/file_XHRResponseURL_nocors.text
@@ -0,0 +1 @@
+data
diff --git a/dom/xhr/tests/file_XHRSendData.sjs b/dom/xhr/tests/file_XHRSendData.sjs
new file mode 100644
index 000000000..0ccbe8a45
--- /dev/null
+++ b/dom/xhr/tests/file_XHRSendData.sjs
@@ -0,0 +1,30 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response)
+{
+ if (request.hasHeader("Content-Type"))
+ response.setHeader("Result-Content-Type",
+ request.getHeader("Content-Type"));
+
+ response.setHeader("Content-Type", "text/plain; charset=ISO-8859-1");
+
+ var body = new BinaryInputStream(request.bodyInputStream);
+ var avail;
+ var bytes = [];
+ while ((avail = body.available()) > 0)
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.setHeader("Result-Content-Length", "" + data.length);
+ if (data.indexOf("TEST_REDIRECT_STR") >= 0) {
+ var newURL = "http://" + data.split("&url=")[1];
+ response.setStatusLine(null, 307, "redirect");
+ response.setHeader("Location", newURL, false);
+ }
+ else {
+ response.write(data);
+ }
+}
diff --git a/dom/xhr/tests/file_XHRSendData_doc.xml b/dom/xhr/tests/file_XHRSendData_doc.xml
new file mode 100644
index 000000000..2e385258c
--- /dev/null
+++ b/dom/xhr/tests/file_XHRSendData_doc.xml
@@ -0,0 +1,2 @@
+<!-- comment -->
+<out>hi</out>
diff --git a/dom/xhr/tests/file_XHRSendData_doc.xml^headers^ b/dom/xhr/tests/file_XHRSendData_doc.xml^headers^
new file mode 100644
index 000000000..b0f1ebfbf
--- /dev/null
+++ b/dom/xhr/tests/file_XHRSendData_doc.xml^headers^
@@ -0,0 +1 @@
+Content-Type: application/xml; charset=ISO-8859-1
diff --git a/dom/xhr/tests/file_XHR_anon.sjs b/dom/xhr/tests/file_XHR_anon.sjs
new file mode 100644
index 000000000..755c4cdf3
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_anon.sjs
@@ -0,0 +1,24 @@
+function handleRequest(request, response) {
+ let invalidHeaders = ["Cookie"];
+ let headers = {};
+
+ if (request.queryString == "expectAuth=true") {
+ if (request.hasHeader("Authorization")) {
+ headers["authorization"] = request.getHeader("Authorization");
+ } else {
+ response.setStatusLine(null, 401, "Authentication required");
+ response.setHeader("WWW-Authenticate", "basic realm=\"testrealm\"", true);
+ }
+ } else {
+ invalidHeaders.push("Authorization");
+ }
+
+ for (let header of invalidHeaders) {
+ if (request.hasHeader(header)) {
+ response.setStatusLine(null, 500, "Server Error");
+ headers[header.toLowerCase()] = request.getHeader(header);
+ }
+ }
+
+ response.write(JSON.stringify(headers));
+}
diff --git a/dom/xhr/tests/file_XHR_binary1.bin b/dom/xhr/tests/file_XHR_binary1.bin
new file mode 100644
index 000000000..39e527bfc
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_binary1.bin
Binary files differ
diff --git a/dom/xhr/tests/file_XHR_binary1.bin^headers^ b/dom/xhr/tests/file_XHR_binary1.bin^headers^
new file mode 100644
index 000000000..8e8c8d859
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_binary1.bin^headers^
@@ -0,0 +1 @@
+Content-Type: application/binary
diff --git a/dom/xhr/tests/file_XHR_binary2.bin b/dom/xhr/tests/file_XHR_binary2.bin
new file mode 100644
index 000000000..9f442b092
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_binary2.bin
Binary files differ
diff --git a/dom/xhr/tests/file_XHR_fail1.txt b/dom/xhr/tests/file_XHR_fail1.txt
new file mode 100644
index 000000000..462209d8d
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_fail1.txt
@@ -0,0 +1 @@
+redirect file
diff --git a/dom/xhr/tests/file_XHR_fail1.txt^headers^ b/dom/xhr/tests/file_XHR_fail1.txt^headers^
new file mode 100644
index 000000000..41c235932
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_fail1.txt^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: http://example.com/tests/dom/xhr/tests/file_XHR_pass2.txt
diff --git a/dom/xhr/tests/file_XHR_fail1b.txt b/dom/xhr/tests/file_XHR_fail1b.txt
new file mode 100644
index 000000000..8944657af
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_fail1b.txt
@@ -0,0 +1 @@
+hello pass
diff --git a/dom/xhr/tests/file_XHR_header.sjs b/dom/xhr/tests/file_XHR_header.sjs
new file mode 100644
index 000000000..937e90a02
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_header.sjs
@@ -0,0 +1,6 @@
+// SJS file for getAllResponseRequests vs getResponseRequest
+function handleRequest(request, response)
+{
+ response.setHeader("X-Custom-Header-Bytes", "…", false);
+ response.write("42");
+}
diff --git a/dom/xhr/tests/file_XHR_pass1.xml b/dom/xhr/tests/file_XHR_pass1.xml
new file mode 100644
index 000000000..06826d6c6
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_pass1.xml
@@ -0,0 +1 @@
+<res>hello</res>
diff --git a/dom/xhr/tests/file_XHR_pass2.txt b/dom/xhr/tests/file_XHR_pass2.txt
new file mode 100644
index 000000000..0d7f879f9
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_pass2.txt
@@ -0,0 +1 @@
+hello pass
diff --git a/dom/xhr/tests/file_XHR_pass3.txt b/dom/xhr/tests/file_XHR_pass3.txt
new file mode 100644
index 000000000..462209d8d
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_pass3.txt
@@ -0,0 +1 @@
+redirect file
diff --git a/dom/xhr/tests/file_XHR_pass3.txt^headers^ b/dom/xhr/tests/file_XHR_pass3.txt^headers^
new file mode 100644
index 000000000..fb5056c38
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_pass3.txt^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: file_XHR_pass2.txt
diff --git a/dom/xhr/tests/file_XHR_system_redirect.html b/dom/xhr/tests/file_XHR_system_redirect.html
new file mode 100644
index 000000000..8267b01d0
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_system_redirect.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<body>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/xhr/tests/file_XHR_system_redirect.html^headers^ b/dom/xhr/tests/file_XHR_system_redirect.html^headers^
new file mode 100644
index 000000000..cefb165b0
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_system_redirect.html^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Found
+Location: file:///etc/passwd
diff --git a/dom/xhr/tests/file_XHR_timeout.sjs b/dom/xhr/tests/file_XHR_timeout.sjs
new file mode 100644
index 000000000..b349d122e
--- /dev/null
+++ b/dom/xhr/tests/file_XHR_timeout.sjs
@@ -0,0 +1,15 @@
+var timer = null;
+
+function handleRequest(request, response)
+{
+ response.processAsync();
+ timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(function()
+ {
+ response.setStatusLine(null, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write("hello");
+ response.finish();
+ }, 3000 /* milliseconds */, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/dom/xhr/tests/file_html_in_xhr.html b/dom/xhr/tests/file_html_in_xhr.html
new file mode 100644
index 000000000..d77aeb440
--- /dev/null
+++ b/dom/xhr/tests/file_html_in_xhr.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html><!-- Þ -->
+<meta charset="Windows-1251">
+<script>
+document.documentElement.setAttribute("data-fail", "FAIL");
+</script>
+<script src="file_html_in_xhr.sjs"></script>
+<script src="file_html_in_xhr.sjs" defer></script>
+<script src="file_html_in_xhr.sjs" async></script>
+<link type="stylesheet" href="file_html_in_xhr.sjs">
+<body onload='document.documentElement.setAttribute("data-fail", "FAIL");'>
+<img src="file_html_in_xhr.sjs">
+<iframe src="file_html_in_xhr.sjs"></iframe>
+<video poster="file_html_in_xhr.sjs" src="file_html_in_xhr.sjs"></video>
+<object data="file_html_in_xhr.sjs"></object>
+<noscript><div></div></noscript>
diff --git a/dom/xhr/tests/file_html_in_xhr.sjs b/dom/xhr/tests/file_html_in_xhr.sjs
new file mode 100644
index 000000000..8f4075ae7
--- /dev/null
+++ b/dom/xhr/tests/file_html_in_xhr.sjs
@@ -0,0 +1,15 @@
+function handleRequest(request, response)
+{
+ response.setHeader("Content-Type", "text/javascript", false);
+ if (request.queryString.indexOf("report") != -1) {
+ if (getState("loaded") == "loaded") {
+ response.write("ok(false, 'This script was not supposed to get fetched.'); continueAfterReport();");
+ } else {
+ response.write("ok(true, 'This script was not supposed to get fetched.'); continueAfterReport();");
+ }
+ } else {
+ setState("loaded", "loaded");
+ response.write('document.documentElement.setAttribute("data-fail", "FAIL");');
+ }
+}
+
diff --git a/dom/xhr/tests/file_html_in_xhr2.html b/dom/xhr/tests/file_html_in_xhr2.html
new file mode 100644
index 000000000..046052c75
--- /dev/null
+++ b/dom/xhr/tests/file_html_in_xhr2.html
@@ -0,0 +1 @@
+<meta charset="windows-1251">Þ
diff --git a/dom/xhr/tests/file_html_in_xhr3.html b/dom/xhr/tests/file_html_in_xhr3.html
new file mode 100644
index 000000000..ff43ca409
--- /dev/null
+++ b/dom/xhr/tests/file_html_in_xhr3.html
@@ -0,0 +1 @@
+SUCCESS
diff --git a/dom/xhr/tests/file_sync_xhr_document_write_with_iframe.html b/dom/xhr/tests/file_sync_xhr_document_write_with_iframe.html
new file mode 100644
index 000000000..060389427
--- /dev/null
+++ b/dom/xhr/tests/file_sync_xhr_document_write_with_iframe.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<body>
+<script>
+function syncXHR() {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", window.location, false);
+ xhr.send(null);
+}
+
+addEventListener('load', evt => {
+ syncXHR();
+ document.open();
+ document.write(
+ '<body>' +
+ '<iframe src="about:blank"></iframe>' +
+ '<script>window.opener.postMessage("DONE", "*");</' + 'script>' +
+ '</body>');
+ document.close();
+}, { once: true });
+</script>
+</body>
diff --git a/dom/xhr/tests/iframe_sync_xhr_unload.html b/dom/xhr/tests/iframe_sync_xhr_unload.html
new file mode 100644
index 000000000..d58b1994c
--- /dev/null
+++ b/dom/xhr/tests/iframe_sync_xhr_unload.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <script type="application/javascript">
+
+function o() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "sync_xhr_unload.sjs", false);
+ try { xhr.send(); } catch(e) {}
+}
+
+window.addEventListener("beforeunload", o, false);
+window.addEventListener("unload", o, false)
+
+ </script>
+</body>
+</html>
+
diff --git a/dom/xhr/tests/mochitest.ini b/dom/xhr/tests/mochitest.ini
new file mode 100644
index 000000000..35ba1e3db
--- /dev/null
+++ b/dom/xhr/tests/mochitest.ini
@@ -0,0 +1,115 @@
+[DEFAULT]
+support-files =
+ echo.sjs
+ temporaryFileBlob.sjs
+ file_html_in_xhr.html
+ file_html_in_xhr.sjs
+ file_html_in_xhr2.html
+ file_html_in_xhr3.html
+ file_XHRDocURI.text
+ file_XHRDocURI.text^headers^
+ file_XHRDocURI.xml
+ file_XHRDocURI.xml^headers^
+ file_XHRDocURI.html
+ file_XHRDocURI.html^headers^
+ file_XHRDocURI.sjs
+ file_XHRResponseURL.js
+ file_XHRResponseURL.sjs
+ file_XHRResponseURL.text
+ file_XHRResponseURL.text^headers^
+ file_XHRResponseURL_nocors.text
+ file_XHRSendData.sjs
+ file_XHRSendData_doc.xml
+ file_XHRSendData_doc.xml^headers^
+ file_XHR_anon.sjs
+ file_XHR_binary1.bin
+ file_XHR_binary1.bin^headers^
+ file_XHR_binary2.bin
+ file_XHR_fail1.txt
+ file_XHR_fail1.txt^headers^
+ file_XHR_header.sjs
+ file_XHR_pass1.xml
+ file_XHR_pass2.txt
+ file_XHR_pass3.txt
+ file_XHR_pass3.txt^headers^
+ file_XHR_system_redirect.html
+ file_XHR_system_redirect.html^headers^
+ file_XHR_timeout.sjs
+ progressserver.sjs
+ worker_terminateSyncXHR_frame.html
+ terminateSyncXHR_worker.js
+ worker_testXHR.txt
+ xhr_worker.js
+ xhr2_worker.js
+ xhrAbort_worker.js
+ test_worker_xhr_parameters.js
+ test_worker_xhr_system.js
+ worker_xhr_cors_redirect.js
+ worker_xhr_cors_redirect.sjs
+ worker_xhr_headers_server.sjs
+ worker_xhr_headers_worker.js
+ worker_file_getcookie.sjs
+ xhr_implicit_cancel_worker.js
+ relativeLoad_import.js
+ relativeLoad_worker.js
+ relativeLoad_worker2.js
+ responseIdentical.sjs
+ subdir/relativeLoad_sub_worker.js
+ subdir/relativeLoad_sub_worker2.js
+ subdir/relativeLoad_sub_import.js
+ common_temporaryFileBlob.js
+ worker_temporaryFileBlob.js
+ worker_bug1300552.js
+ sync_xhr_unload.sjs
+ iframe_sync_xhr_unload.html
+ empty.html
+ file_sync_xhr_document_write_with_iframe.html
+
+[test_bug1300552.html]
+[test_html_in_xhr.html]
+[test_relativeLoad.html]
+skip-if = buildapp == 'b2g' # b2g(Failed to load script: relativeLoad_import.js) b2g-debug(Failed to load script: relativeLoad_import.js) b2g-desktop(Failed to load script: relativeLoad_import.js)
+[test_sync_xhr_timer.xhtml]
+skip-if = toolkit == 'android'
+[test_sync_xhr_unload.html]
+[test_temporaryFileBlob.html]
+[test_worker_terminateSyncXHR.html]
+skip-if = buildapp == 'b2g'
+[test_worker_xhr.html]
+[test_worker_xhr2.html]
+[test_worker_xhr_3rdparty.html]
+[test_worker_xhr_cors_redirect.html]
+[test_worker_xhr_headers.html]
+[test_worker_xhr_implicit_cancel.html]
+[test_worker_xhr_parameters.html]
+skip-if = buildapp == 'b2g'
+[test_worker_xhr_responseURL.html]
+[test_worker_xhr_system.html]
+[test_worker_xhr_timeout.html]
+[test_worker_xhrAbort.html]
+skip-if = (os == "win") || (os == "mac") || toolkit == 'android' #bug 798220
+[test_XHR.html]
+[test_xhr_abort_after_load.html]
+skip-if = toolkit == 'android'
+[test_XHR_anon.html]
+[test_xhr_forbidden_headers.html]
+[test_XHR_header.html]
+[test_XHR_onuploadprogress.html]
+[test_xhr_overridemimetype_throws_on_invalid_state.html]
+skip-if = buildapp == 'b2g' # Requires webgl support
+[test_XHR_parameters.html]
+skip-if = buildapp == 'b2g' # b2g(86 total, 4 failing - testing mozAnon - got false, expected true) b2g-debug(86 total, 4 failing - testing mozAnon - got false, expected true) b2g-desktop(86 total, 4 failing - testing mozAnon - got false, expected true)
+[test_xhr_progressevents.html]
+skip-if = toolkit == 'android'
+[test_xhr_send.html]
+[test_xhr_send_readystate.html]
+[test_XHR_system.html]
+skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(12 total, 2 failing - .mozSystem == true - got false, expected true + ) b2g-desktop(12 total, 2 failing - .mozSystem == true - got false, expected true + )
+[test_XHR_timeout.html]
+skip-if = buildapp == 'b2g' || (android_version == '18' && debug) # b2g(flaky on B2G, bug 960743) b2g-debug(flaky on B2G, bug 960743) b2g-desktop(flaky on B2G, bug 960743)
+support-files = test_XHR_timeout.js
+[test_xhr_withCredentials.html]
+[test_XHRDocURI.html]
+[test_XHRResponseURL.html]
+[test_XHRSendData.html]
+[test_sync_xhr_document_write_with_iframe.html]
diff --git a/dom/xhr/tests/progressserver.sjs b/dom/xhr/tests/progressserver.sjs
new file mode 100644
index 000000000..945f79557
--- /dev/null
+++ b/dom/xhr/tests/progressserver.sjs
@@ -0,0 +1,52 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function setReq(req) {
+ setObjectState("dom/xhr/tests/progressserver", req);
+}
+
+function getReq() {
+ var req;
+ getObjectState("dom/xhr/tests/progressserver", function(v) {
+ req = v;
+ });
+ return req;
+}
+
+function handleRequest(request, response)
+{
+ var pairs = request.queryString.split('&');
+ var command = pairs.shift();
+ dump("received '" + command + "' command\n");
+
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+ var body = "";
+ var bodyAvail;
+ while ((bodyAvail = bodyStream.available()) > 0)
+ body += String.fromCharCode.apply(null, bodyStream.readByteArray(bodyAvail));
+
+ if (command == "open") {
+ response.processAsync();
+ setReq(response);
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ pairs.forEach(function (val) {
+ var [name, value] = val.split('=');
+ response.setHeader(name, unescape(value), false);
+ });
+ response.write(body);
+ return;
+ }
+
+ if (command == "send") {
+ getReq().write(body);
+ }
+ else if (command == "close") {
+ getReq().finish();
+ setReq(null);
+ }
+ response.setHeader("Content-Type", "text/plain");
+ response.write("ok");
+}
diff --git a/dom/xhr/tests/relativeLoad_import.js b/dom/xhr/tests/relativeLoad_import.js
new file mode 100644
index 000000000..114d1883c
--- /dev/null
+++ b/dom/xhr/tests/relativeLoad_import.js
@@ -0,0 +1,5 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+const workerURL = "relativeLoad_worker.js";
diff --git a/dom/xhr/tests/relativeLoad_worker.js b/dom/xhr/tests/relativeLoad_worker.js
new file mode 100644
index 000000000..42959db30
--- /dev/null
+++ b/dom/xhr/tests/relativeLoad_worker.js
@@ -0,0 +1,27 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+const importURL = "relativeLoad_import.js";
+
+onmessage = function(event) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "worker_testXHR.txt", false);
+ xhr.send(null);
+ if (xhr.status != 200 ||
+ xhr.responseText != "A noisy noise annoys an oyster.") {
+ throw "Couldn't get xhr text from where we wanted it!";
+ }
+
+ importScripts(importURL);
+ var worker = new Worker("relativeLoad_worker2.js");
+ worker.onerror = function(event) {
+ throw event.message;
+ };
+ worker.onmessage = function(event) {
+ if (event.data != workerURL) {
+ throw "Bad data!";
+ }
+ postMessage(workerURL);
+ }
+};
diff --git a/dom/xhr/tests/relativeLoad_worker2.js b/dom/xhr/tests/relativeLoad_worker2.js
new file mode 100644
index 000000000..e9fbdf903
--- /dev/null
+++ b/dom/xhr/tests/relativeLoad_worker2.js
@@ -0,0 +1,9 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+const importURL = "relativeLoad_import.js";
+
+importScripts(importURL);
+
+postMessage(workerURL);
diff --git a/dom/xhr/tests/responseIdentical.sjs b/dom/xhr/tests/responseIdentical.sjs
new file mode 100644
index 000000000..f1986819a
--- /dev/null
+++ b/dom/xhr/tests/responseIdentical.sjs
@@ -0,0 +1,17 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+// Simply sending back the same data that is received
+function handleRequest(request, response)
+{
+ var body = "";
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+ var bytes = [], avail = 0;
+ while ((avail = bodyStream.available()) > 0)
+ body += String.fromCharCode.apply(String, bodyStream.readByteArray(avail));
+
+ response.setHeader("Content-Type", "application/octet-stream", false);
+ response.write(body);
+}
diff --git a/dom/xhr/tests/subdir/relativeLoad_sub_import.js b/dom/xhr/tests/subdir/relativeLoad_sub_import.js
new file mode 100644
index 000000000..cac2a6f04
--- /dev/null
+++ b/dom/xhr/tests/subdir/relativeLoad_sub_import.js
@@ -0,0 +1,5 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+const workerSubURL = "subdir/relativeLoad_sub_worker.js";
diff --git a/dom/xhr/tests/subdir/relativeLoad_sub_worker.js b/dom/xhr/tests/subdir/relativeLoad_sub_worker.js
new file mode 100644
index 000000000..f725cfb61
--- /dev/null
+++ b/dom/xhr/tests/subdir/relativeLoad_sub_worker.js
@@ -0,0 +1,26 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+const importSubURL = "relativeLoad_sub_import.js";
+
+onmessage = function(event) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "testXHR.txt", false);
+ xhr.send(null);
+ if (xhr.status != 404) {
+ throw "Loaded an xhr from the wrong location!";
+ }
+
+ importScripts(importSubURL);
+ var worker = new Worker("relativeLoad_sub_worker2.js");
+ worker.onerror = function(event) {
+ throw event.data;
+ };
+ worker.onmessage = function(event) {
+ if (event.data != workerSubURL) {
+ throw "Bad data!";
+ }
+ postMessage(workerSubURL);
+ };
+};
diff --git a/dom/xhr/tests/subdir/relativeLoad_sub_worker2.js b/dom/xhr/tests/subdir/relativeLoad_sub_worker2.js
new file mode 100644
index 000000000..f47a29ee6
--- /dev/null
+++ b/dom/xhr/tests/subdir/relativeLoad_sub_worker2.js
@@ -0,0 +1,9 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+const importSubURL = "relativeLoad_sub_import.js";
+
+importScripts(importSubURL);
+
+postMessage(workerSubURL);
diff --git a/dom/xhr/tests/sync_xhr_unload.sjs b/dom/xhr/tests/sync_xhr_unload.sjs
new file mode 100644
index 000000000..4185056e3
--- /dev/null
+++ b/dom/xhr/tests/sync_xhr_unload.sjs
@@ -0,0 +1,15 @@
+var timer = null;
+
+function handleRequest(request, response)
+{
+ response.processAsync();
+ timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(function()
+ {
+ response.setStatusLine(null, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write("hello");
+ response.finish();
+ }, 30000 /* milliseconds */, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/dom/xhr/tests/temporaryFileBlob.sjs b/dom/xhr/tests/temporaryFileBlob.sjs
new file mode 100644
index 000000000..ff3410c78
--- /dev/null
+++ b/dom/xhr/tests/temporaryFileBlob.sjs
@@ -0,0 +1,33 @@
+const CC = Components.Constructor;
+
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream");
+const Timer = CC("@mozilla.org/timer;1",
+ "nsITimer",
+ "initWithCallback");
+
+function handleRequest(request, response) {
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+ var bodyBytes = [];
+ while ((bodyAvail = bodyStream.available()) > 0)
+ Array.prototype.push.apply(bodyBytes, bodyStream.readByteArray(bodyAvail));
+
+ var bos = new BinaryOutputStream(response.bodyOutputStream);
+
+ response.processAsync();
+
+ var part = bodyBytes.splice(0, 256);
+ bos.writeByteArray(part, part.length);
+
+ response.timer1 = new Timer(function(timer) {
+ bos.writeByteArray(bodyBytes, bodyBytes.length);
+ }, 1000, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+
+ response.timer2 = new Timer(function(timer) {
+ response.finish();
+ }, 2000, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/dom/xhr/tests/terminateSyncXHR_worker.js b/dom/xhr/tests/terminateSyncXHR_worker.js
new file mode 100644
index 000000000..c86baeb42
--- /dev/null
+++ b/dom/xhr/tests/terminateSyncXHR_worker.js
@@ -0,0 +1,19 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function(event) {
+ throw "No messages should reach me!";
+}
+
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "worker_testXHR.txt", false);
+xhr.addEventListener("loadstart", function ()
+{
+ // Tell the parent to terminate us.
+ postMessage("TERMINATE");
+ // And wait for it to do so.
+ while(1) { true; }
+});
+xhr.send(null);
diff --git a/dom/xhr/tests/test_XHR.html b/dom/xhr/tests/test_XHR.html
new file mode 100644
index 000000000..143805a9e
--- /dev/null
+++ b/dom/xhr/tests/test_XHR.html
@@ -0,0 +1,393 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for XMLHttpRequest</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="gen.next();">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.7">
+"use strict";
+SimpleTest.waitForExplicitFinish();
+
+var gen = runTests();
+function continueTest() { gen.next(); }
+
+function runTests() {
+
+var path = "/tests/dom/xhr/tests/";
+
+var passFiles = [['file_XHR_pass1.xml', 'GET', 200, 'OK', 'text/xml'],
+ ['file_XHR_pass2.txt', 'GET', 200, 'OK', 'text/plain'],
+ ['file_XHR_pass3.txt', 'GET', 200, 'OK', 'text/plain'],
+ ['data:text/xml,%3Cres%3Ehello%3C/res%3E%0A', 'GET', 200, 'OK', 'text/xml'],
+ ['data:text/plain,hello%20pass%0A', 'GET', 200, 'OK', 'text/plain'],
+ ['data:,foo', 'GET', 200, 'OK', 'text/plain;charset=US-ASCII', 'foo'],
+ ['data:text/plain;base64,Zm9v', 'GET', 200, 'OK', 'text/plain', 'foo'],
+ ['data:text/plain,foo#bar', 'GET', 200, 'OK', 'text/plain', 'foo'],
+ ['data:text/plain,foo%23bar', 'GET', 200, 'OK', 'text/plain', 'foo#bar'],
+ ];
+
+var blob = new Blob(["foo"], { type: "text/plain" });
+var blobURL = URL.createObjectURL(blob);
+
+passFiles.push([blobURL, 'GET', 200, 'OK', 'text/plain', 'foo']);
+
+var failFiles = [['//example.com' + path + 'file_XHR_pass1.xml', 'GET'],
+ ['ftp://localhost' + path + 'file_XHR_pass1.xml', 'GET'],
+ ['file_XHR_fail1.txt', 'GET'],
+ ];
+
+for (i = 0; i < passFiles.length; ++i) {
+ // Function to give our hacked is() a scope
+ (function(oldIs) {
+ function is(actual, expected, message) {
+ oldIs(actual, expected, message + " for " + passFiles[i][0]);
+ }
+ xhr = new XMLHttpRequest();
+ is(xhr.getResponseHeader("Content-Type"), null, "should be null");
+ is(xhr.getAllResponseHeaders(), "", "should be empty string");
+ is(xhr.responseType, "", "wrong initial responseType");
+ xhr.open(passFiles[i][1], passFiles[i][0], false);
+ xhr.send(null);
+ is(xhr.status, passFiles[i][2], "wrong status");
+ is(xhr.statusText, passFiles[i][3], "wrong statusText");
+ is(xhr.getResponseHeader("Content-Type"), passFiles[i][4], "wrong content type");
+ var headers = xhr.getAllResponseHeaders();
+ ok(/(?:^|\n)Content-Type:\s*([^\r\n]*)\r\n/i.test(headers) &&
+ RegExp.$1 === passFiles[i][4], "wrong response headers");
+ if (xhr.responseXML) {
+ is((new XMLSerializer()).serializeToString(xhr.responseXML.documentElement),
+ passFiles[i][5] || "<res>hello</res>", "wrong responseXML");
+ is(xhr.response, passFiles[i][5] || "<res>hello</res>\n", "wrong response");
+ }
+ else {
+ is(xhr.responseText, passFiles[i][5] || "hello pass\n", "wrong responseText");
+ is(xhr.response, passFiles[i][5] || "hello pass\n", "wrong response");
+ }
+ })(is);
+}
+
+URL.revokeObjectURL(blobURL);
+
+for (i = 0; i < failFiles.length; ++i) {
+ xhr = new XMLHttpRequest();
+ var didthrow = false;
+ try {
+ xhr.open(failFiles[i][1], failFiles[i][0], false);
+ xhr.send(null);
+ }
+ catch (e) {
+ didthrow = true;
+ }
+ if (!didthrow) {
+ is(xhr.status, 301, "wrong status");
+ is(xhr.responseText, "redirect file\n", "wrong response");
+ }
+ else {
+ ok(1, "should have thrown or given incorrect result");
+ }
+}
+
+function checkResponseTextAccessThrows(xhr) {
+ var didthrow = false;
+ try { xhr.responseText } catch (e) { didthrow = true; }
+ ok(didthrow, "should have thrown when accessing responseText");
+}
+function checkResponseXMLAccessThrows(xhr) {
+ var didthrow = false;
+ try { xhr.responseXML } catch (e) { didthrow = true; }
+ ok(didthrow, "should have thrown when accessing responseXML");
+}
+function checkSetResponseType(xhr, type) {
+ var didthrow = false;
+ try { xhr.responseType = type; } catch (e) { didthrow = true; }
+ is(xhr.responseType, type, "responseType should be " + type);
+ ok(!didthrow, "should not have thrown when setting responseType");
+}
+function checkSetResponseTypeThrows(xhr, type) {
+ var didthrow = false;
+ try { xhr.responseType = type; } catch (e) { didthrow = true; }
+ ok(didthrow, "should have thrown when setting responseType");
+}
+function checkOpenThrows(xhr, method, url, async) {
+ var didthrow = false;
+ try { xhr.open(method, url, async); } catch (e) { didthrow = true; }
+ ok(didthrow, "should have thrown when open is called");
+}
+
+// test if setting responseType before calling open() works
+xhr = new XMLHttpRequest();
+checkSetResponseType(xhr, "");
+checkSetResponseType(xhr, "text");
+checkSetResponseType(xhr, "document");
+checkSetResponseType(xhr, "arraybuffer");
+checkSetResponseType(xhr, "blob");
+checkSetResponseType(xhr, "json");
+checkSetResponseType(xhr, "moz-chunked-text");
+checkSetResponseType(xhr, "moz-chunked-arraybuffer");
+checkOpenThrows(xhr, "GET", "file_XHR_pass2.txt", false);
+
+// test response (sync, responseType is not changeable)
+xhr = new XMLHttpRequest();
+xhr.open("GET", 'file_XHR_pass2.txt', false);
+checkSetResponseTypeThrows(xhr, "");
+checkSetResponseTypeThrows(xhr, "text");
+checkSetResponseTypeThrows(xhr, "document");
+checkSetResponseTypeThrows(xhr, "arraybuffer");
+checkSetResponseTypeThrows(xhr, "blob");
+checkSetResponseTypeThrows(xhr, "json");
+checkSetResponseTypeThrows(xhr, "moz-chunked-text");
+checkSetResponseTypeThrows(xhr, "moz-chunked-arraybuffer");
+xhr.send(null);
+checkSetResponseTypeThrows(xhr, "document");
+is(xhr.status, 200, "wrong status");
+is(xhr.response, "hello pass\n", "wrong response");
+
+// test response (responseType='document')
+xhr = new XMLHttpRequest();
+xhr.open("GET", 'file_XHR_pass1.xml');
+xhr.responseType = 'document';
+xhr.onloadend = continueTest;
+xhr.send(null);
+yield undefined;
+checkSetResponseTypeThrows(xhr, "document");
+is(xhr.status, 200, "wrong status");
+checkResponseTextAccessThrows(xhr);
+is((new XMLSerializer()).serializeToString(xhr.response.documentElement),
+ "<res>hello</res>",
+ "wrong response");
+
+// test response (responseType='text')
+xhr = new XMLHttpRequest();
+xhr.open("GET", 'file_XHR_pass2.txt');
+xhr.responseType = 'text';
+xhr.onloadend = continueTest;
+xhr.send(null);
+yield undefined;
+is(xhr.status, 200, "wrong status");
+checkResponseXMLAccessThrows(xhr);
+is(xhr.response, "hello pass\n", "wrong response");
+
+// test response (responseType='arraybuffer')
+function arraybuffer_equals_to(ab, s) {
+ is(ab.byteLength, s.length, "wrong arraybuffer byteLength");
+
+ var u8v = new Uint8Array(ab);
+ is(String.fromCharCode.apply(String, u8v), s, "wrong values");
+}
+
+// with a simple text file
+xhr = new XMLHttpRequest();
+xhr.open("GET", 'file_XHR_pass2.txt');
+xhr.responseType = 'arraybuffer';
+xhr.onloadend = continueTest;
+xhr.send(null);
+yield undefined;
+is(xhr.status, 200, "wrong status");
+checkResponseTextAccessThrows(xhr);
+checkResponseXMLAccessThrows(xhr);
+var ab = xhr.response;
+ok(ab != null, "should have a non-null arraybuffer");
+arraybuffer_equals_to(ab, "hello pass\n");
+
+// test reusing the same XHR (Bug 680816)
+xhr.open("GET", 'file_XHR_binary1.bin');
+xhr.responseType = 'arraybuffer';
+xhr.onloadend = continueTest;
+xhr.send(null);
+yield undefined;
+is(xhr.status, 200, "wrong status");
+var ab2 = xhr.response;
+ok(ab2 != null, "should have a non-null arraybuffer");
+ok(ab2 != ab, "arraybuffer on XHR reuse should be distinct");
+arraybuffer_equals_to(ab, "hello pass\n");
+arraybuffer_equals_to(ab2, "\xaa\xee\0\x03\xff\xff\xff\xff\xbb\xbb\xbb\xbb");
+
+// with a binary file
+xhr = new XMLHttpRequest();
+xhr.open("GET", 'file_XHR_binary1.bin');
+xhr.responseType = 'arraybuffer';
+xhr.onloadend = continueTest;
+xhr.send(null);
+yield undefined;
+is(xhr.status, 200, "wrong status");
+checkResponseTextAccessThrows(xhr);
+checkResponseXMLAccessThrows(xhr);
+ab = xhr.response;
+ok(ab != null, "should have a non-null arraybuffer");
+arraybuffer_equals_to(ab, "\xaa\xee\0\x03\xff\xff\xff\xff\xbb\xbb\xbb\xbb");
+is(xhr.response, xhr.response, "returns the same ArrayBuffer");
+
+// test response (responseType='json')
+var xhr = new XMLHttpRequest();
+xhr.open("POST", 'responseIdentical.sjs');
+xhr.responseType = 'json';
+var jsonObjStr = JSON.stringify({title: "aBook", author: "john"});
+xhr.onloadend = continueTest;
+xhr.send(jsonObjStr);
+yield undefined;
+is(xhr.status, 200, "wrong status");
+checkResponseTextAccessThrows(xhr);
+checkResponseXMLAccessThrows(xhr);
+is(JSON.stringify(xhr.response), jsonObjStr, "correct result");
+is(xhr.response, xhr.response, "returning the same object on each access");
+
+// with invalid json
+var xhr = new XMLHttpRequest();
+xhr.open("POST", 'responseIdentical.sjs');
+xhr.responseType = 'json';
+xhr.onloadend = continueTest;
+xhr.send("{");
+yield undefined;
+is(xhr.status, 200, "wrong status");
+checkResponseTextAccessThrows(xhr);
+checkResponseXMLAccessThrows(xhr);
+is(xhr.response, null, "Bad JSON should result in null response.");
+is(xhr.response, null, "Bad JSON should result in null response even 2nd time.");
+
+// Test status/statusText in all readyStates
+xhr = new XMLHttpRequest();
+function checkXHRStatus() {
+ if (xhr.readyState == xhr.UNSENT || xhr.readyState == xhr.OPENED) {
+ is(xhr.status, 0, "should be 0 before getting data");
+ is(xhr.statusText, "", "should be empty before getting data");
+ }
+ else {
+ is(xhr.status, 200, "should be 200 when we have data");
+ is(xhr.statusText, "OK", "should be OK when we have data");
+ }
+}
+checkXHRStatus();
+xhr.open("GET", 'file_XHR_binary1.bin');
+checkXHRStatus();
+xhr.responseType = 'arraybuffer';
+xhr.send(null);
+xhr.onreadystatechange = continueTest;
+while (xhr.readyState != 4) {
+ checkXHRStatus();
+ yield undefined;
+}
+checkXHRStatus();
+
+// test response (responseType='blob')
+var responseTypes = ['blob', 'moz-blob'];
+for (var i = 0; i < responseTypes.length; i++) {
+ var t = responseTypes[i];
+ // with a simple text file
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", 'file_XHR_pass2.txt');
+ xhr.responseType = t;
+ xhr.onloadend = continueTest;
+ xhr.send(null);
+ yield undefined;
+ is(xhr.status, 200, "wrong status");
+ checkResponseTextAccessThrows(xhr);
+ checkResponseXMLAccessThrows(xhr);
+ var b = xhr.response;
+ ok(b, "should have a non-null blob");
+ ok(b instanceof Blob, "should be a Blob");
+ ok(!(b instanceof File), "should not be a File");
+ is(b.size, "hello pass\n".length, "wrong blob size");
+
+ var fr = new FileReader();
+ fr.onload = continueTest;
+ fr.readAsBinaryString(b);
+ yield undefined;
+ ok(fr.result, "hello pass\n", "wrong values");
+
+ // with a binary file
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", 'file_XHR_binary1.bin', true);
+ xhr.send(null);
+ xhr.onreadystatechange = continueTest;
+ while(xhr.readyState != 2)
+ yield undefined;
+
+ is(xhr.status, 200, "wrong status");
+ xhr.responseType = t;
+
+ while(xhr.readyState != 4)
+ yield undefined;
+
+ xhr.onreadystatechange = null;
+
+ b = xhr.response;
+ ok(b != null, "should have a non-null blob");
+ is(b.size, 12, "wrong blob size");
+
+ fr = new FileReader();
+ fr.readAsBinaryString(b);
+ xhr = null; // kill the XHR object
+ b = null;
+ SpecialPowers.gc();
+ fr.onload = continueTest;
+ yield undefined;
+ is(fr.result, "\xaa\xee\0\x03\xff\xff\xff\xff\xbb\xbb\xbb\xbb", "wrong values");
+
+ // with a larger binary file
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", 'file_XHR_binary2.bin', true);
+ xhr.responseType = t;
+ xhr.send(null);
+ xhr.onreadystatechange = continueTest;
+
+ while (xhr.readyState != 4)
+ yield undefined;
+
+ xhr.onreadystatechange = null;
+
+ var b = xhr.response;
+ ok(b != null, "should have a non-null blob");
+ is(b.size, 65536, "wrong blob size");
+
+ fr = new FileReader();
+ fr.readAsArrayBuffer(b);
+ fr.onload = continueTest;
+ xhr = null; // kill the XHR object
+ b = null;
+ SpecialPowers.gc();
+ yield undefined;
+
+ var u8 = new Uint8Array(fr.result);
+ for (var i = 0; i < 65536; i++) {
+ if (u8[i] !== (i & 255)) {
+ break;
+ }
+ }
+ is(i, 65536, "wrong value at offset " + i);
+}
+
+var client = new XMLHttpRequest();
+client.open("GET", "file_XHR_pass1.xml", true);
+client.send();
+client.onreadystatechange = function() {
+ if(client.readyState == 4) {
+ try {
+ is(client.responseXML, null, "responseXML should be null.");
+ is(client.responseText, "", "responseText should be empty string.");
+ is(client.response, "", "response should be empty string.");
+ is(client.status, 0, "status should be 0.");
+ is(client.statusText, "", "statusText should be empty string.");
+ is(client.getAllResponseHeaders(), "",
+ "getAllResponseHeaders() should return empty string.");
+ } catch(ex) {
+ ok(false, "Shouldn't throw! [" + ex + "]");
+ }
+ }
+}
+client.abort();
+
+SimpleTest.finish();
+yield undefined;
+
+} /* runTests */
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_XHRDocURI.html b/dom/xhr/tests/test_XHRDocURI.html
new file mode 100644
index 000000000..e3bd1d311
--- /dev/null
+++ b/dom/xhr/tests/test_XHRDocURI.html
@@ -0,0 +1,513 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=459470
+-->
+<head>
+ <title>XMLHttpRequest return document URIs</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <base href="http://example.org/">
+</head>
+<body onload="startTest();">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=459470">Mozilla Bug 459470</a><br />
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=859095">Mozilla Bug 859095</a>
+
+<p id="display">
+<iframe id=loader></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+SimpleTest.waitForExplicitFinish();
+var gen;
+
+function startTest() {
+ // The test uses history API, so don't do anything before load event has been
+ // handled.
+ SimpleTest.executeSoon(function() {
+ gen = runTest();
+ gen.next();
+ });
+}
+
+function testXMLDocURI(aDoc, aExpects) {
+ is(aDoc.documentURI, aExpects.documentURI, "wrong url");
+ is(aDoc.baseURI, aExpects.baseURI, "wrong base");
+ is(aDoc.documentElement.baseURI, aExpects.elementBaseURI,
+ "wrong base (xml:base)");
+}
+
+function testChromeXMLDocURI(aDoc, aExpects) {
+ is(aDoc.documentURI, aExpects.documentURI, "wrong url");
+ is(aDoc.documentURIObject.spec, aExpects.documentURI,
+ "wrong url (.documentObjectURI)");
+ is(aDoc.baseURI, aExpects.baseURI, "wrong base");
+ is(aDoc.baseURIObject.spec, aExpects.baseURI,
+ "wrong base (.baseURIObject)");
+ is(aDoc.documentElement.baseURI, aExpects.elementBaseURI,
+ "wrong base (xml:base)");
+ is(aDoc.documentElement.baseURIObject.spec, aExpects.elementBaseURI,
+ "wrong base (.baseURIObject, xml:base)");
+}
+
+function testHTMLDocURI(aDoc, aExpects) {
+ is(aDoc.documentURI, aExpects.documentURI, "wrong url");
+ is(aDoc.baseURI, aExpects.baseURI, "wrong base");
+
+ var base = aDoc.createElement("base");
+ var newBaseURI = "http://www.example.com/";
+ base.href = newBaseURI;
+ aDoc.head.appendChild(base);
+ is(aDoc.baseURI, newBaseURI, "wrong base (after <base> changed)");
+}
+
+function testChromeHTMLDocURI(aDoc, aNonChromeBaseURI, aExpects) {
+ is(aDoc.documentURI, aExpects.documentURI, "wrong url");
+ is(aDoc.documentURIObject.spec, aExpects.documentURI,
+ "wrong url (.documentURIObject)");
+ is(aDoc.baseURI, aExpects.baseURI, "wrong base");
+ is(aDoc.baseURIObject.spec, aExpects.baseURI,
+ "wrong url (.baseURIObject)");
+
+ aDoc.body.setAttributeNS("http://www.w3.org/XML/1998/namespace", "base",
+ aNonChromeBaseURI);
+ is(aDoc.body.baseURI, aNonChromeBaseURI,
+ "wrong base (doc base and xml:base are same)");
+ is(aDoc.body.baseURIObject.spec, aNonChromeBaseURI,
+ "wrong base (.baseURIObject, doc base and xml:base are same)")
+ var attr = aDoc.getElementById("data").getAttributeNode("id");
+ is(attr.baseURI, aNonChromeBaseURI,
+ "wrong attr base (doc base and xml:base are same)")
+ is(attr.baseURIObject.spec, aNonChromeBaseURI,
+ "wrong attr base (.baseURIObject, doc base and xml:base are same)")
+
+ var base = aDoc.createElement("base");
+ var newBaseURI = "http://www.example.com/";
+ base.href = newBaseURI;
+ aDoc.head.appendChild(base);
+ is(aDoc.baseURI, newBaseURI, "wrong base (after <base> changed)");
+ is(aDoc.baseURIObject.spec, newBaseURI,
+ "wrong base (.baseURIObject, after <base> changed)");
+}
+
+function testCloneDocURI(aDoc) {
+ var clone = aDoc.cloneNode(true);
+ is(clone.documentURI, aDoc.documentURI, "wrong url (clone)");
+ is(clone.baseURI, aDoc.baseURI, "wrong base (clone)");
+}
+
+function runTest() {
+ is(document.baseURI, "http://example.org/", "wrong doc baseURI");
+
+ // use content XHR and access URI properties from content privileged script
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml");
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.responseXML) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ baseURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ elementBaseURI: "http://www.example.com/"
+ };
+ testXMLDocURI(xhr.responseXML, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html");
+ xhr.responseType = "document";
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.response) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html",
+ baseURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html"
+ };
+ testHTMLDocURI(xhr.response, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.text");
+ xhr.onreadystatechange = function(e) {
+ is(xhr.responseXML, null, "should not have document");
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml");
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.responseXML) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ baseURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ elementBaseURI: "http://www.example.com/"
+ };
+ testXMLDocURI(xhr.responseXML, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html");
+ xhr.responseType = "document";
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.response) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html",
+ baseURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html"
+ };
+ testHTMLDocURI(xhr.response, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml");
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.responseXML) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ baseURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ elementBaseURI: "http://www.example.com/"
+ };
+ testXMLDocURI(xhr.responseXML, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html");
+ xhr.responseType = "document";
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.response) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html",
+ baseURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html"
+ };
+ testHTMLDocURI(xhr.response, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.text");
+ xhr.onreadystatechange = function(e) {
+ is(xhr.responseXML, null, "should not have document");
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+
+ // use content XHR and access URI properties from chrome privileged script
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml");
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.responseXML) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ baseURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ elementBaseURI: "http://www.example.com/"
+ };
+ var xml = SpecialPowers.wrap(xhr.responseXML);
+ testChromeXMLDocURI(xml, expects);
+ testCloneDocURI(xml);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html");
+ xhr.responseType = "document";
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.response) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html",
+ baseURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html"
+ };
+ var doc = SpecialPowers.wrap(xhr.response);
+ testChromeHTMLDocURI(doc, "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html", expects);
+ testCloneDocURI(doc);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml");
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.responseXML) {
+ return;
+ }
+ var expects = {
+ documentURI: document.documentURI,
+ baseURI: document.baseURI,
+ elementBaseURI: "http://www.example.com/"
+ };
+ var xml = SpecialPowers.wrap(xhr.responseXML);
+ testChromeXMLDocURI(xml, expects);
+ testCloneDocURI(xml);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html");
+ xhr.responseType = "document";
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.response) {
+ return;
+ }
+ var expects = {
+ documentURI: document.documentURI,
+ baseURI: document.baseURI
+ };
+ var doc = SpecialPowers.wrap(xhr.response);
+ testChromeHTMLDocURI(doc, "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html", expects);
+ testCloneDocURI(doc);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml");
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.responseXML) {
+ return;
+ }
+ var expects = {
+ documentURI: document.documentURI,
+ baseURI: document.baseURI,
+ elementBaseURI: "http://www.example.com/"
+ };
+ var xml = SpecialPowers.wrap(xhr.responseXML);
+ testChromeXMLDocURI(xml, expects);
+ testCloneDocURI(xml);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html");
+ xhr.responseType = "document";
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.response) {
+ return;
+ }
+ var expects = {
+ documentURI: document.documentURI,
+ baseURI: document.baseURI
+ };
+ var doc = SpecialPowers.wrap(xhr.response);
+ testChromeHTMLDocURI(doc, "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html", expects);
+ testCloneDocURI(doc);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+
+ // use the systemXHR special privilege
+ SpecialPowers.addPermission("systemXHR", true, document);
+ xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml");
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.responseXML) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ baseURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ elementBaseURI: "http://www.example.com/"
+ };
+ testXMLDocURI(xhr.responseXML, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html");
+ xhr.responseType = "document";
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.response) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html",
+ baseURI: "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.html"
+ };
+ testHTMLDocURI(xhr.response, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+ xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml");
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.responseXML) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ baseURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ elementBaseURI: "http://www.example.com/"
+ };
+ testXMLDocURI(xhr.responseXML, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+ xhr.open("GET", "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html");
+ xhr.responseType = "document";
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.response) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html",
+ baseURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html"
+ };
+ testHTMLDocURI(xhr.response, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml");
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.responseXML) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ baseURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.xml",
+ elementBaseURI: "http://www.example.com/"
+ };
+ testXMLDocURI(xhr.responseXML, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+ xhr.open("GET", "http://mochi.test:8888/tests/dom/xhr/tests/file_XHRDocURI.sjs?url=http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html");
+ xhr.responseType = "document";
+ xhr.onreadystatechange = function(e) {
+ if (!xhr.response) {
+ return;
+ }
+ var expects = {
+ documentURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html",
+ baseURI: "http://example.com/tests/dom/xhr/tests/file_XHRDocURI.html"
+ };
+ testHTMLDocURI(xhr.response, expects);
+ if (xhr.readyState == 4) {
+ gen.next();
+ }
+ };
+ xhr.send();
+ yield undefined;
+
+ history.pushState({}, "pushStateTest", window.location.href + "/pushStateTest");
+ ok(document.documentURI.indexOf("pushStateTest") > -1);
+
+ var chromeDoc = SpecialPowers.wrap(document);
+ ok(chromeDoc.documentURI.indexOf("pushStateTest") > -1);
+
+ SimpleTest.executeSoon(function() { gen.next(); });
+
+ yield undefined;
+
+ history.back();
+ SimpleTest.executeSoon(function() { gen.next(); });
+
+ yield undefined;
+
+ SimpleTest.finish();
+ SpecialPowers.removePermission("systemXHR", document);
+ yield undefined;
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_XHRResponseURL.html b/dom/xhr/tests/test_XHRResponseURL.html
new file mode 100644
index 000000000..17e8c627a
--- /dev/null
+++ b/dom/xhr/tests/test_XHRResponseURL.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=998076
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 998076</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="file_XHRResponseURL.js"></script>
+ <script type="application/javascript">
+
+/** Test for Bug 998076 **/
+"use strict";
+
+window.addEventListener("message", function (aEvent) {
+ var data = aEvent.data;
+ if (data === "done") {
+ SimpleTest.finish();
+ return;
+ }
+ if (data === "start") {
+ return;
+ }
+ if (data.type === "is") {
+ SimpleTest.is(data.actual, data.expected, data.message);
+ window.postMessage("pong", "*");
+ return;
+ }
+ if (data.type === "ok") {
+ SimpleTest.ok(data.bool, data.message);
+ window.postMessage("pong", "*");
+ return;
+ }
+ if (data.type === "info") {
+ SimpleTest.info(data.message);
+ window.postMessage("pong", "*");
+ return;
+ }
+ if (data.type === "todo") {
+ SimpleTest.todo(data.bool, data.message);
+ window.postMessage("pong", "*");
+ return;
+ }
+ if (data.type === "todo_is") {
+ SimpleTest.todo_is(data.actual, data.expected, data.message);
+ window.postMessage("pong", "*");
+ return;
+ }
+});
+
+function runTests() {
+ SimpleTest.waitForExplicitFinish();
+ window.postMessage("start", "*");
+}
+
+ </script>
+</head>
+<body onload="runTests()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=998076">Mozilla Bug 998076</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_XHRSendData.html b/dom/xhr/tests/test_XHRSendData.html
new file mode 100644
index 000000000..c210efc9c
--- /dev/null
+++ b/dom/xhr/tests/test_XHRSendData.html
@@ -0,0 +1,267 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=464848
+-->
+<head>
+ <title>XMLHttpRequest send data and headers</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="createFiles();">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=464848">Mozilla Bug 464848</a>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+var testData = "blahblahblahblahblahblahblaaaaaaaah. blah.";
+var extensions = [".txt",".png",".jpg",".gif",".xml", "noext"];
+var fileTypes = ["text/plain", "image/png", "image/jpeg", "image/gif", "text/xml", null];
+var gen = runTests();
+var testDOMFiles;
+
+function createFiles() {
+ var filesToCreate = new Array();
+ extensions.forEach(function (extension) {
+ filesToCreate.push({name: "testfile" + extension, data: testData});
+ });
+ SpecialPowers.createFiles(filesToCreate,
+ function (files) {
+ testDOMFiles = files;
+ gen.next();
+ },
+ function (msg) {
+ testDOMFiles = new Array;
+ ok(false, "File creation error: " + msg);
+ gen.next();
+ });
+};
+
+
+function continueTest() { gen.next(); }
+
+function runTests() {
+xhr = new XMLHttpRequest();
+xhr.open("GET", "file_XHRSendData_doc.xml", false);
+xhr.send();
+testDoc1 = xhr.responseXML;
+is(testDoc1.inputEncoding, "windows-1252", "wrong encoding");
+
+testDoc2 = document.implementation.createDocument("", "", null);
+testDoc2.appendChild(testDoc2.createComment(" doc 2 "));
+testDoc2.appendChild(testDoc2.createElement("res"));
+testDoc2.documentElement.appendChild(testDoc2.createTextNode("text"));
+is(testDoc2.inputEncoding, "UTF-8", "wrong encoding");
+
+// arraybuffer test objects
+var shortArray = new ArrayBuffer(1);
+var shortInt8View = new Uint8Array(shortArray);
+shortInt8View[0] = 3;
+
+var longArray = new ArrayBuffer(512);
+var longInt8View = new Uint8Array(longArray);
+for (var i = 0; i < longInt8View.length; i++) {
+ longInt8View[i] = i % 255;
+}
+
+// arraybufferview test objects
+var longArraySlice = longArray.slice(256, 384);
+var longInt32View1 = new Int32Array(longArraySlice)
+var longInt32View2 = new Int32Array(longArray, 256, 32)
+var longInt16View1 = new Uint16Array(longArraySlice)
+var longInt16View2 = new Uint16Array(longArray, 256, 64)
+var longInt8View1 = new Int8Array(longArraySlice)
+var longInt8View2 = new Int8Array(longArray, 256, 128)
+
+tests = [{ body: null,
+ resBody: "",
+ },
+ { body: undefined,
+ resBody: "",
+ },
+ { body: "hi",
+ resBody: "hi",
+ resContentType: "text/plain;charset=UTF-8",
+ },
+ { body: "r\xe4ksm\xf6rg\xe5s",
+ resBody: "r\xc3\xa4ksm\xc3\xb6rg\xc3\xa5s",
+ resContentType: "text/plain;charset=UTF-8",
+ },
+ { body: "hi",
+ contentType: "",
+ resBody: "hi",
+ resContentType: "text/plain;charset=UTF-8",
+ },
+ { body: "hi",
+ contentType: "foo/bar",
+ resBody: "hi",
+ resContentType: "foo/bar",
+ },
+ { body: "hi",
+ contentType: "foo/bar; baz=bin",
+ resBody: "hi",
+ resContentType: "foo/bar; baz=bin",
+ },
+ { body: "hi",
+ contentType: "foo/bar; charset=ascii; baz=bin",
+ resBody: "hi",
+ resContentType: "foo/bar; charset=UTF-8; baz=bin",
+ },
+ { body: "hi",
+ contentType: "foo/bar; charset=uTf-8",
+ resBody: "hi",
+ resContentType: "foo/bar; charset=uTf-8",
+ },
+ { body: testDoc1,
+ resBody: "<!-- comment -->\n<out>hi</out>",
+ resContentType: "application/xml;charset=UTF-8",
+ },
+ { body: testDoc1,
+ contentType: "foo/bar",
+ resBody: "<!-- comment -->\n<out>hi</out>",
+ resContentType: "foo/bar",
+ },
+ { body: testDoc1,
+ contentType: "foo/bar; charset=ascii; baz=bin",
+ resBody: "<!-- comment -->\n<out>hi</out>",
+ resContentType: "foo/bar; charset=UTF-8; baz=bin",
+ },
+ { body: testDoc1,
+ contentType: "foo/bar; charset=wIndows-1252",
+ resBody: "<!-- comment -->\n<out>hi</out>",
+ resContentType: "foo/bar; charset=UTF-8",
+ },
+ { body: testDoc2,
+ resBody: "<!-- doc 2 -->\n<res>text</res>",
+ resContentType: "application/xml;charset=UTF-8",
+ },
+ { body: testDoc2,
+ contentType: "foo/bar",
+ resBody: "<!-- doc 2 -->\n<res>text</res>",
+ resContentType: "foo/bar",
+ },
+ { body: testDoc2,
+ contentType: "foo/bar; charset=ascii; baz=bin",
+ resBody: "<!-- doc 2 -->\n<res>text</res>",
+ resContentType: "foo/bar; charset=UTF-8; baz=bin",
+ },
+ { body: testDoc2,
+ contentType: "foo/bar; charset=uTf-8",
+ resBody: "<!-- doc 2 -->\n<res>text</res>",
+ resContentType: "foo/bar; charset=uTf-8",
+ },
+ { //will trigger a redirect test server-side
+ body: ("TEST_REDIRECT_STR&url=" + window.location.host + window.location.pathname),
+ redirect: true,
+ },
+ { body: shortArray,
+ resBody: shortArray,
+ resType: "arraybuffer"
+ },
+ { body: longArray,
+ resBody: longArray,
+ resType: "arraybuffer"
+ },
+ { body: longInt32View1,
+ resBody: longArraySlice,
+ resType: "arraybuffer"
+ },
+ { body: longInt32View2,
+ resBody: longArraySlice,
+ resType: "arraybuffer"
+ },
+ { body: longInt16View1,
+ resBody: longArraySlice,
+ resType: "arraybuffer"
+ },
+ { body: longInt16View2,
+ resBody: longArraySlice,
+ resType: "arraybuffer"
+ },
+ { body: longInt8View1,
+ resBody: longArraySlice,
+ resType: "arraybuffer"
+ },
+ { body: longInt8View2,
+ resBody: longArraySlice,
+ resType: "arraybuffer"
+ },
+ ];
+
+for (var i = 0; i < testDOMFiles.length; i++) {
+ tests.push({ body: testDOMFiles[i],
+ resBody: testData,
+ resContentType: fileTypes[i],
+ resContentLength: testData.length,
+ });
+}
+
+try {
+ for (test of tests) {
+ xhr = new XMLHttpRequest;
+ xhr.open("POST", "file_XHRSendData.sjs", !!test.resType);
+ if (test.contentType)
+ xhr.setRequestHeader("Content-Type", test.contentType);
+ if (test.resType) {
+ xhr.responseType = test.resType;
+ xhr.onloadend = continueTest;
+ }
+ xhr.send(test.body);
+ if (test.resType)
+ yield undefined;
+
+ if (test.resContentType) {
+ is(xhr.getResponseHeader("Result-Content-Type"), test.resContentType,
+ "Wrong Content-Type sent");
+ }
+ else {
+ is(xhr.getResponseHeader("Result-Content-Type"), null);
+ }
+
+ if (test.resContentLength) {
+ is(xhr.getResponseHeader("Result-Content-Length"),
+ String(test.resContentLength),
+ "Wrong Content-Length sent");
+ }
+
+ if (test.resType == "arraybuffer") {
+ is_identical_arraybuffer(xhr.response, test.resBody);
+ }
+ else if (test.body instanceof Document) {
+ is(xhr.responseText.replace("\r\n", "\n"), test.resBody, "Wrong body");
+ }
+ else if (!test.redirect) {
+ is(xhr.responseText, test.resBody, "Wrong body");
+ }
+ else {
+ // If we're testing redirect, determine whether the body is
+ // this document by looking for the relevant bug url
+ is(xhr.responseText.indexOf("https://bugzilla.mozilla.org/show_bug.cgi?id=464848") >= 0, true,
+ "Wrong page for redirect");
+ }
+ }
+} catch (e) {
+}
+
+function is_identical_arraybuffer(ab1, ab2) {
+ is(ab1.byteLength, ab2.byteLength, "arraybuffer byteLengths not equal");
+ u8v1 = new Uint8Array(ab1);
+ u8v2 = new Uint8Array(ab2);
+ is(String.fromCharCode.apply(String, u8v1),
+ String.fromCharCode.apply(String, u8v2), "arraybuffer values not equal");
+}
+
+SimpleTest.finish();
+yield undefined;
+} /* runTests */
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_XHR_anon.html b/dom/xhr/tests/test_XHR_anon.html
new file mode 100644
index 000000000..ed9271a04
--- /dev/null
+++ b/dom/xhr/tests/test_XHR_anon.html
@@ -0,0 +1,180 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for XMLHttpRequest with system privileges</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setup();">
+<p id="display">
+<iframe id="loader"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+// An XHR with the anon flag set will not send cookie and auth information.
+const TEST_URL = "http://example.com/tests/dom/xhr/tests/file_XHR_anon.sjs";
+document.cookie = "foo=bar";
+
+let am = {
+ authMgr: null,
+
+ init: function() {
+ this.authMgr = SpecialPowers.Cc["@mozilla.org/network/http-auth-manager;1"]
+ .getService(SpecialPowers.Ci.nsIHttpAuthManager)
+ },
+
+ addIdentity: function() {
+ this.authMgr.setAuthIdentity("http", "example.com", -1, "basic", "testrealm",
+ "", "example.com", "user1", "password1");
+ },
+
+ tearDown: function() {
+ this.authMgr.clearAll();
+ },
+}
+
+var tests = [ test1, test2, test2a, test3, test3, test3, test4, test4, test4, test5, test5, test5 ];
+
+function runTests() {
+ if (!tests.length) {
+ am.tearDown();
+
+ // Resetting the cookie.
+ document.cookie = "foo=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+function test1() {
+ am.addIdentity();
+
+ let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ is(xhr.mozAnon, true, "test1: .mozAnon == true");
+ xhr.open("GET", TEST_URL);
+ xhr.onload = function onload() {
+ is(xhr.status, 200, "test1: " + xhr.responseText);
+ am.tearDown();
+ runTests();
+ };
+ xhr.onerror = function onerror() {
+ ok(false, "Got an error event!");
+ am.tearDown();
+ runTests();
+ }
+ xhr.send();
+}
+
+function test2() {
+ am.addIdentity();
+
+ let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ is(xhr.mozAnon, true, "test2: .mozAnon == true");
+ xhr.open("GET", TEST_URL + "?expectAuth=true", true,
+ "user2name", "pass2word");
+ xhr.onload = function onload() {
+ is(xhr.status, 200, "test2: " + xhr.responseText);
+ let response = JSON.parse(xhr.responseText);
+ is(response.authorization, "Basic dXNlcjJuYW1lOnBhc3Myd29yZA==");
+ am.tearDown();
+ runTests();
+ };
+ xhr.onerror = function onerror() {
+ ok(false, "Got an error event!");
+ am.tearDown();
+ runTests();
+ }
+ xhr.send();
+}
+
+function test2a() {
+ am.addIdentity();
+
+ let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ is(xhr.mozAnon, true, "test2: .mozAnon == true");
+ xhr.open("GET", TEST_URL + "?expectAuth=true", true,
+ "user1", "pass2word");
+ xhr.onload = function onload() {
+ is(xhr.status, 200, "test2: " + xhr.responseText);
+ let response = JSON.parse(xhr.responseText);
+ is(response.authorization, "Basic dXNlcjE6cGFzczJ3b3Jk");
+ am.tearDown();
+ runTests();
+ };
+ xhr.onerror = function onerror() {
+ ok(false, "Got an error event!");
+ am.tearDown();
+ runTests();
+ }
+ xhr.send();
+}
+
+function test3() {
+ am.addIdentity();
+
+ let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ is(xhr.mozAnon, true, "test3: .mozAnon == true");
+ xhr.open("GET", TEST_URL + "?expectAuth=true", true);
+ xhr.onload = function onload() {
+ is(xhr.status, 401, "test3: " + xhr.responseText);
+ am.tearDown();
+ runTests();
+ };
+ xhr.onerror = function onerror() {
+ ok(false, "Got an error event!");
+ am.tearDown();
+ runTests();
+ }
+ xhr.send();
+}
+
+function test4() {
+ let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ is(xhr.mozAnon, true, "test4: .mozAnon == true");
+ xhr.open("GET", TEST_URL + "?expectAuth=true", true);
+ xhr.onload = function onload() {
+ is(xhr.status, 401, "test4: " + xhr.responseText);
+ runTests();
+ };
+ xhr.onerror = function onerror() {
+ ok(false, "Got an error event!");
+ runTests();
+ }
+ xhr.send();
+}
+
+function test5() {
+ let xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ is(xhr.mozAnon, true, "test5: .mozAnon == true");
+ xhr.open("GET", TEST_URL + "?expectAuth=true", true,
+ "user2name", "pass2word");
+ xhr.onload = function onload() {
+ is(xhr.status, 200, "test5: " + xhr.responseText);
+ let response = JSON.parse(xhr.responseText);
+ is(response.authorization, "Basic dXNlcjJuYW1lOnBhc3Myd29yZA==");
+ runTests();
+ };
+ xhr.onerror = function onerror() {
+ ok(false, "Got an error event!");
+ runTests();
+ }
+ xhr.send();
+}
+
+function setup() {
+ am.init();
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTests);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_XHR_header.html b/dom/xhr/tests/test_XHR_header.html
new file mode 100644
index 000000000..e213b25c7
--- /dev/null
+++ b/dom/xhr/tests/test_XHR_header.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for XMLHttpRequest.GetResponseHeader(foo) byte-inflates the output</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <meta charset="utf-8">
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.7">
+"use strict";
+SimpleTest.waitForExplicitFinish();
+
+let xhr = new XMLHttpRequest();
+xhr.open('GET', 'file_XHR_header.sjs', true);
+xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ ok(xhr.getResponseHeader('X-Custom-Header-Bytes') == "\xE2\x80\xA6", 'getResponseHeader byte-inflates the output');
+ SimpleTest.finish();
+ }
+}
+xhr.send(null);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_XHR_onuploadprogress.html b/dom/xhr/tests/test_XHR_onuploadprogress.html
new file mode 100644
index 000000000..10c4fd20d
--- /dev/null
+++ b/dom/xhr/tests/test_XHR_onuploadprogress.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=743666
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 743666</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=743666">Mozilla Bug 743666</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 743666 **/
+
+var called = false;
+function uploadprogress()
+{
+ called = true;
+}
+
+var xhr = new XMLHttpRequest();
+xhr.upload.onprogress = uploadprogress;
+var event = new ProgressEvent("progress");
+xhr.upload.dispatchEvent(event);
+ok(called,
+ "XMLHttpRequest.upload.onprogress sets upload progress event listener");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_XHR_parameters.html b/dom/xhr/tests/test_XHR_parameters.html
new file mode 100644
index 000000000..b99bb9dfe
--- /dev/null
+++ b/dom/xhr/tests/test_XHR_parameters.html
@@ -0,0 +1,97 @@
+
+
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for XMLHttpRequest with system privileges</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="runTests();">
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+function runTests() {
+ SimpleTest.waitForExplicitFinish();
+
+ let validParameters = [
+ undefined,
+ null,
+ {},
+ {mozSystem: ""},
+ {mozSystem: 0},
+ {mozAnon: 1},
+ {mozAnon: []},
+ {get mozAnon() { return true; }},
+ 0,
+ 7,
+ Math.PI,
+ "string",
+ true,
+ false,
+ ];
+
+ let invalidParameters = [
+ {get mozSystem() { throw "Bla"; } },
+ ];
+
+ let havePrivileges = false;
+
+ function testValidParameter(value) {
+ let xhr;
+ try {
+ xhr = new XMLHttpRequest(value);
+ } catch (ex) {
+ ok(false, "Got unexpected exception: " + ex);
+ return;
+ }
+ ok(xhr instanceof XMLHttpRequest, "passed " + JSON.stringify(value));
+
+ // If the page doesnt have privileges to create a system XHR,
+ // this flag will always be false no matter what is passed.
+ let expectedAnon = Boolean(value && value.mozAnon);
+ let expectedSystem = false;
+ if (havePrivileges) {
+ expectedSystem = Boolean(value && value.mozSystem);
+ }
+ is(xhr.mozAnon, expectedAnon, "testing mozAnon");
+ is(xhr.mozSystem, expectedSystem, "testing mozSystem");
+ }
+
+ function testInvalidParameter(value) {
+ let expectedError;
+ try {
+ new XMLHttpRequest(value);
+ ok(false, "invalid parameter did not cause exception: " +
+ JSON.stringify(value));
+ } catch (ex) {
+ expectedError = ex;
+ }
+ ok(expectedError, "invalid parameter raised exception as expected: " +
+ JSON.stringify(expectedError))
+ }
+
+ // Run the tests once without API privileges...
+ validParameters.forEach(testValidParameter);
+ invalidParameters.forEach(testInvalidParameter);
+
+ // ...and once with privileges.
+ havePrivileges = true;
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], function() {
+ validParameters.forEach(testValidParameter);
+ invalidParameters.forEach(testInvalidParameter);
+
+ SimpleTest.finish();
+ });
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_XHR_system.html b/dom/xhr/tests/test_XHR_system.html
new file mode 100644
index 000000000..1d61e3f77
--- /dev/null
+++ b/dom/xhr/tests/test_XHR_system.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for XMLHttpRequest with system privileges</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="runTests();">
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+let tests = [];
+
+const PROTECTED_URL = "file:///etc/passwd";
+const REDIRECT_URL = window.location.protocol + "//" + window.location.host + "/tests/dom/xhr/tests/file_XHR_system_redirect.html";
+const CROSSSITE_URL = "http://example.com/tests/dom/xhr/tests/test_XHR_system.html";
+
+tests.push(function test_cross_origin() {
+ // System XHR can load cross-origin resources.
+
+ is(window.location.hostname, "mochi.test", "correct origin");
+
+ let xhr = new XMLHttpRequest({mozSystem: true});
+ is(xhr.mozSystem, true, ".mozSystem == true");
+ xhr.open("GET", CROSSSITE_URL);
+ xhr.onload = function onload() {
+ is(xhr.status, 200, "correct HTTP status");
+ ok(xhr.responseText != null, "HTTP response non-null");
+ ok(xhr.responseText.length, "HTTP response not empty");
+ runNextTest();
+ };
+ xhr.onerror = function onerror(event) {
+ ok(false, "Got an error event: " + event);
+ runNextTest();
+ }
+ xhr.send();
+});
+
+tests.push(function test_file_uri() {
+ // System XHR is not permitted to access file:/// URIs.
+
+ let xhr = new XMLHttpRequest({mozSystem: true});
+ is(xhr.mozSystem, true, ".mozSystem == true");
+ xhr.open("GET", PROTECTED_URL);
+ xhr.onload = function() {
+ ok(false, "Should not have loaded");
+ runNextTest();
+ }
+ xhr.onerror = function(event) {
+ ok(true, "Got an error event: " + event);
+ is(xhr.status, 0, "HTTP status is 0");
+ runNextTest();
+ }
+ xhr.send();
+});
+
+tests.push(function test_redirect_to_file_uri() {
+ // System XHR won't load file:/// URIs even if an HTTP resource redirects there.
+
+ let xhr = new XMLHttpRequest({mozSystem: true});
+ is(xhr.mozSystem, true, ".mozSystem == true");
+ xhr.open("GET", REDIRECT_URL);
+ xhr.onload = function onload() {
+ ok(false, "Should not have loaded");
+ runNextTest();
+ };
+ xhr.onerror = function onerror(event) {
+ ok(true, "Got an error event: " + event);
+ is(xhr.status, 0, "HTTP status is 0");
+ runNextTest();
+ }
+ xhr.send();
+});
+
+
+function runNextTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+ tests.shift()();
+}
+
+function runTests() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runNextTest);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_XHR_timeout.html b/dom/xhr/tests/test_XHR_timeout.html
new file mode 100644
index 000000000..14422d9f4
--- /dev/null
+++ b/dom/xhr/tests/test_XHR_timeout.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=525816
+-->
+<head>
+ <title>Test for Bug 525816</title>
+ <script type="application/javascript"
+ src="/MochiKit/MochiKit.js"></script>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet"
+ type="text/css"
+ href="/tests/SimpleTest/test.css">
+</head>
+<body>
+
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=525816"
+ >Mozilla Bug 525816 (XMLHttpRequest timeout)</a>
+<p id="display"></p>
+<div id="content">
+ This test takes over 1 minute to run, probably over 2 minutes.
+</div>
+<pre id="test">
+<script class="testbody"
+ type="text/javascript"
+ src="test_XHR_timeout.js"></script>
+<script type="text/javascript">
+ window.addEventListener("message", function (event) {
+ if (event.data == "done") {
+ SimpleTest.finish();
+ return;
+ }
+ if (event.data == "start") {
+ return;
+ }
+ if (event.data.type == "is") {
+ SimpleTest.is(event.data.got, event.data.expected, event.data.msg);
+ return;
+ }
+ if (event.data.type == "ok") {
+ SimpleTest.ok(event.data.bool, event.data.msg);
+ return;
+ }
+ });
+ // Final test harness setup and launch.
+ (function() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestLongerTimeout(TestRequests.length);
+ SimpleTest.requestFlakyTimeout("This is testing XHR timeouts.");
+ var msg = "This test will take approximately " + (TestRequests.length * 10)
+ msg += " seconds to complete, at most.";
+ document.getElementById("content").firstChild.nodeValue = msg;
+ window.postMessage("start", "*");
+ })();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_XHR_timeout.js b/dom/xhr/tests/test_XHR_timeout.js
new file mode 100644
index 000000000..203700887
--- /dev/null
+++ b/dom/xhr/tests/test_XHR_timeout.js
@@ -0,0 +1,342 @@
+/* Notes:
+ - All times are expressed in milliseconds in this test suite.
+ - Test harness code is at the end of this file.
+ - We generate only one request at a time, to avoid overloading the HTTP
+ request handlers.
+ */
+
+var inWorker = false;
+try {
+ inWorker = !(self instanceof Window);
+} catch (e) {
+ inWorker = true;
+}
+
+function message(data) {
+ if (inWorker)
+ self.postMessage(data);
+ else
+ self.postMessage(data, "*");
+}
+
+function is(got, expected, msg) {
+ var obj = {};
+ obj.type = "is";
+ obj.got = got;
+ obj.expected = expected;
+ obj.msg = msg;
+
+ message(obj);
+}
+
+function ok(bool, msg) {
+ var obj = {};
+ obj.type = "ok";
+ obj.bool = bool;
+ obj.msg = msg;
+
+ message(obj);
+}
+
+/**
+ * Generate and track results from a XMLHttpRequest with regards to timeouts.
+ *
+ * @param {String} id The test description.
+ * @param {Number} timeLimit The initial setting for the request timeout.
+ * @param {Number} resetAfter (Optional) The time after sending the request, to
+ * reset the timeout.
+ * @param {Number} resetTo (Optional) The delay to reset the timeout to.
+ *
+ * @note The actual testing takes place in handleEvent(event).
+ * The requests are generated in startXHR().
+ *
+ * @note If resetAfter and resetTo are omitted, only the initial timeout setting
+ * applies.
+ *
+ * @constructor
+ * @implements DOMEventListener
+ */
+function RequestTracker(async, id, timeLimit /*[, resetAfter, resetTo]*/) {
+ this.async = async;
+ this.id = id;
+ this.timeLimit = timeLimit;
+
+ if (arguments.length > 3) {
+ this.mustReset = true;
+ this.resetAfter = arguments[3];
+ this.resetTo = arguments[4];
+ }
+
+ this.hasFired = false;
+}
+RequestTracker.prototype = {
+ /**
+ * Start the XMLHttpRequest!
+ */
+ startXHR: function() {
+ var req = new XMLHttpRequest();
+ this.request = req;
+ req.open("GET", "file_XHR_timeout.sjs", this.async);
+ var me = this;
+ function handleEvent(e) { return me.handleEvent(e); };
+ req.onerror = handleEvent;
+ req.onload = handleEvent;
+ req.onabort = handleEvent;
+ req.ontimeout = handleEvent;
+
+ req.timeout = this.timeLimit;
+
+ if (this.mustReset) {
+ var resetTo = this.resetTo;
+ self.setTimeout(function() {
+ req.timeout = resetTo;
+ }, this.resetAfter);
+ }
+
+ req.send(null);
+ },
+
+ /**
+ * Get a message describing this test.
+ *
+ * @returns {String} The test description.
+ */
+ getMessage: function() {
+ var rv = this.id + ", ";
+ if (this.mustReset) {
+ rv += "original timeout at " + this.timeLimit + ", ";
+ rv += "reset at " + this.resetAfter + " to " + this.resetTo;
+ }
+ else {
+ rv += "timeout scheduled at " + this.timeLimit;
+ }
+ return rv;
+ },
+
+ /**
+ * Check the event received, and if it's the right (and only) one we get.
+ *
+ * @param {DOMProgressEvent} evt An event of type "load" or "timeout".
+ */
+ handleEvent: function(evt) {
+ if (this.hasFired) {
+ ok(false, "Only one event should fire: " + this.getMessage());
+ return;
+ }
+ this.hasFired = true;
+
+ var type = evt.type, expectedType;
+ // The XHR responds after 3000 milliseconds with a load event.
+ var timeLimit = this.mustReset && (this.resetAfter < Math.min(3000, this.timeLimit)) ?
+ this.resetTo :
+ this.timeLimit;
+ if ((timeLimit == 0) || (timeLimit >= 3000)) {
+ expectedType = "load";
+ }
+ else {
+ expectedType = "timeout";
+ }
+ is(type, expectedType, this.getMessage());
+ TestCounter.testComplete();
+ }
+};
+
+/**
+ * Generate and track XMLHttpRequests which will have abort() called on.
+ *
+ * @param shouldAbort {Boolean} True if we should call abort at all.
+ * @param abortDelay {Number} The time in ms to wait before calling abort().
+ */
+function AbortedRequest(shouldAbort, abortDelay) {
+ this.shouldAbort = shouldAbort;
+ this.abortDelay = abortDelay;
+ this.hasFired = false;
+}
+AbortedRequest.prototype = {
+ /**
+ * Start the XMLHttpRequest!
+ */
+ startXHR: function() {
+ var req = new XMLHttpRequest();
+ this.request = req;
+ req.open("GET", "file_XHR_timeout.sjs");
+ var me = this;
+ function handleEvent(e) { return me.handleEvent(e); };
+ req.onerror = handleEvent;
+ req.onload = handleEvent;
+ req.onabort = handleEvent;
+ req.ontimeout = handleEvent;
+
+ req.timeout = 2000;
+ var _this = this;
+
+ function abortReq() {
+ req.abort();
+ }
+
+ if (!this.shouldAbort) {
+ self.setTimeout(function() {
+ try {
+ _this.noEventsFired();
+ }
+ catch (e) {
+ ok(false, "Unexpected error: " + e);
+ TestCounter.testComplete();
+ }
+ }, 5000);
+ }
+ else {
+ // Abort events can only be triggered on sent requests.
+ req.send();
+ if (this.abortDelay == -1) {
+ abortReq();
+ }
+ else {
+ self.setTimeout(abortReq, this.abortDelay);
+ }
+ }
+ },
+
+ /**
+ * Ensure that no events fired at all, especially not our timeout event.
+ */
+ noEventsFired: function() {
+ ok(!this.hasFired, "No events should fire for an unsent, unaborted request");
+ // We're done; if timeout hasn't fired by now, it never will.
+ TestCounter.testComplete();
+ },
+
+ /**
+ * Get a message describing this test.
+ *
+ * @returns {String} The test description.
+ */
+ getMessage: function() {
+ return "time to abort is " + this.abortDelay + ", timeout set at 2000";
+ },
+
+ /**
+ * Check the event received, and if it's the right (and only) one we get.
+ *
+ * @param {DOMProgressEvent} evt An event of type "load" or "timeout".
+ */
+ handleEvent: function(evt) {
+ if (this.hasFired) {
+ ok(false, "Only abort event should fire: " + this.getMessage());
+ return;
+ }
+ this.hasFired = true;
+
+ var expectedEvent = (this.abortDelay >= 2000) ? "timeout" : "abort";
+ is(evt.type, expectedEvent, this.getMessage());
+ TestCounter.testComplete();
+ }
+};
+
+var SyncRequestSettingTimeoutAfterOpen = {
+ startXHR: function() {
+ var pass = false;
+ var req = new XMLHttpRequest();
+ req.open("GET", "file_XHR_timeout.sjs", false);
+ try {
+ req.timeout = 1000;
+ }
+ catch (e) {
+ pass = true;
+ }
+ ok(pass, "Synchronous XHR must not allow a timeout to be set");
+ TestCounter.testComplete();
+ }
+};
+
+var SyncRequestSettingTimeoutBeforeOpen = {
+ startXHR: function() {
+ var pass = false;
+ var req = new XMLHttpRequest();
+ req.timeout = 1000;
+ try {
+ req.open("GET", "file_XHR_timeout.sjs", false);
+ }
+ catch (e) {
+ pass = true;
+ }
+ ok(pass, "Synchronous XHR must not allow a timeout to be set");
+ TestCounter.testComplete();
+ }
+};
+
+var TestRequests = [
+ // Simple timeouts.
+ new RequestTracker(true, "no time out scheduled, load fires normally", 0),
+ new RequestTracker(true, "load fires normally", 5000),
+ new RequestTracker(true, "timeout hit before load", 2000),
+
+ // Timeouts reset after a certain delay.
+ new RequestTracker(true, "load fires normally with no timeout set, twice", 0, 2000, 0),
+ new RequestTracker(true, "load fires normally with same timeout set twice", 5000, 2000, 5000),
+ new RequestTracker(true, "timeout fires normally with same timeout set twice", 2000, 1000, 2000),
+
+ new RequestTracker(true, "timeout disabled after initially set", 5000, 2000, 0),
+ new RequestTracker(true, "timeout overrides load after a delay", 5000, 1000, 2000),
+ new RequestTracker(true, "timeout enabled after initially disabled", 0, 2000, 5000),
+
+ new RequestTracker(true, "timeout set to expiring value after load fires", 5000, 4000, 1000),
+ new RequestTracker(true, "timeout set to expired value before load fires", 5000, 2000, 1000),
+ new RequestTracker(true, "timeout set to non-expiring value after timeout fires", 1000, 2000, 5000),
+
+ // Aborted requests.
+ new AbortedRequest(false),
+ new AbortedRequest(true, -1),
+ new AbortedRequest(true, 5000),
+];
+
+var MainThreadTestRequests = [
+ new AbortedRequest(true, 0),
+ new AbortedRequest(true, 1000),
+
+ // Synchronous requests.
+ SyncRequestSettingTimeoutAfterOpen,
+ SyncRequestSettingTimeoutBeforeOpen
+];
+
+var WorkerThreadTestRequests = [
+ // Simple timeouts.
+ new RequestTracker(false, "no time out scheduled, load fires normally", 0),
+ new RequestTracker(false, "load fires normally", 5000),
+ new RequestTracker(false, "timeout hit before load", 2000),
+
+ // Reset timeouts don't make much sense with a sync request ...
+];
+
+if (inWorker) {
+ TestRequests = TestRequests.concat(WorkerThreadTestRequests);
+} else {
+ TestRequests = TestRequests.concat(MainThreadTestRequests);
+}
+
+// This code controls moving from one test to another.
+var TestCounter = {
+ testComplete: function() {
+ // Allow for the possibility there are other events coming.
+ self.setTimeout(function() {
+ TestCounter.next();
+ }, 5000);
+ },
+
+ next: function() {
+ var test = TestRequests.shift();
+
+ if (test) {
+ test.startXHR();
+ }
+ else {
+ message("done");
+ }
+ }
+};
+
+self.addEventListener("message", function (event) {
+ if (event.data == "start") {
+ TestCounter.next();
+ }
+});
diff --git a/dom/xhr/tests/test_bug1300552.html b/dom/xhr/tests/test_bug1300552.html
new file mode 100644
index 000000000..04f076f9d
--- /dev/null
+++ b/dom/xhr/tests/test_bug1300552.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1300552</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+
+var w = new Worker('worker_bug1300552.js');
+w.onmessage = function(e) {
+ if (e.data.type == 'info') {
+ info(e.data.msg);
+ } else if (e.data.type == 'check') {
+ ok(e.data.what, e.data.msg);
+ } else if (e.data.type == 'finish') {
+ SimpleTest.finish();
+ } else {
+ ok(false, 'Something wrong happened');
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+ </script>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_html_in_xhr.html b/dom/xhr/tests/test_html_in_xhr.html
new file mode 100644
index 000000000..ccfb71947
--- /dev/null
+++ b/dom/xhr/tests/test_html_in_xhr.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=651072
+-->
+<head>
+ <title>Test for Bug 651072</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload=runTest();>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=651072">Mozilla Bug 651072</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 651072 **/
+SimpleTest.waitForExplicitFinish();
+
+var xhr = new XMLHttpRequest();
+
+function runTest() {
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 4) {
+ ok(this.responseXML, "Should have gotten responseXML");
+ is(this.responseXML.characterSet, "windows-1251", "Wrong character encoding");
+ is(this.responseXML.documentElement.firstChild.data, " \u042E ", "Decoded using the wrong encoding.");
+ try {
+ this.responseText;
+ ok(false, "responseText access should have thrown.");
+ } catch (e) {
+ is(e.name, "InvalidStateError", "Should have thrown InvalidStateError.");
+ is(e.code, 11, "Should have thrown INVALID_STATE_ERR.");
+ }
+ is(this.responseXML.getElementsByTagName("div").length, 1, "There should be one div.");
+ ok(!this.responseXML.documentElement.hasAttribute("data-fail"), "Should not have a data-fail attribute.");
+ var scripts = this.responseXML.getElementsByTagName("script");
+ is(scripts.length, 4, "Unexpected number of scripts.");
+ while (scripts.length) {
+ // These should not run when moved to another doc
+ document.body.appendChild(scripts[0]);
+ }
+ var s = document.createElement("script");
+ s.src = "file_html_in_xhr.sjs?report=1";
+ document.body.appendChild(s);
+ }
+ }
+ xhr.open("GET", "file_html_in_xhr.html", true);
+ xhr.responseType = "document";
+ xhr.send();
+}
+
+function continueAfterReport() {
+ xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 4) {
+ is(this.responseText.indexOf("\u042E"), -1, "Honored meta in default mode.");
+ is(this.responseText.indexOf("\uFFFD"), 29, "Honored meta in default mode 2.");
+ is(this.responseXML, null, "responseXML should be null for HTML in the default mode");
+ testNonParsingText();
+ }
+ }
+ xhr.open("GET", "file_html_in_xhr2.html");
+ xhr.send();
+}
+
+function testNonParsingText() {
+ xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 4) {
+ is(this.responseText.indexOf("\u042E"), -1, "Honored meta in text mode.");
+ is(this.responseText.indexOf("\uFFFD"), 29, "Honored meta in text mode 2.");
+ testChunkedText();
+ }
+ }
+ xhr.open("GET", "file_html_in_xhr2.html");
+ xhr.responseType = "text";
+ xhr.send();
+}
+
+function testChunkedText() {
+ xhr = new XMLHttpRequest();
+ xhr.onprogress = function() {
+ is(this.responseText.indexOf("\u042E"), -1, "Honored meta in chunked text mode.");
+ }
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 4) {
+ testSyncXHR();
+ }
+ }
+ xhr.open("GET", "file_html_in_xhr2.html");
+ xhr.responseType = "moz-chunked-text";
+ xhr.send();
+}
+
+function testSyncXHR() {
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", "file_html_in_xhr3.html", false);
+ xhr.send();
+ is(xhr.responseText, "SUCCESS\n", "responseText should be ready by now");
+ is(xhr.responseXML, null, "responseXML should be null in the sync case");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xhr/tests/test_relativeLoad.html b/dom/xhr/tests/test_relativeLoad.html
new file mode 100644
index 000000000..fd471b894
--- /dev/null
+++ b/dom/xhr/tests/test_relativeLoad.html
@@ -0,0 +1,52 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads relative load
+-->
+<head>
+ <title>Test for DOM Worker Threads</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var index = -1;
+
+ var urls = [
+ "relativeLoad_worker.js",
+ "subdir/relativeLoad_sub_worker.js"
+ ];
+
+ function messageHandler(event) {
+ if (index >= 0) {
+ is(event.data, urls[index], "Bad url!");
+ if (index == urls.length - 1) {
+ SimpleTest.finish();
+ return;
+ }
+ }
+
+ var worker = new Worker(urls[++index]);
+ worker.onmessage = messageHandler;
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message);
+ SimpleTest.finish();
+ };
+ worker.postMessage("start");
+ }
+
+ messageHandler();
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xhr/tests/test_sync_xhr_document_write_with_iframe.html b/dom/xhr/tests/test_sync_xhr_document_write_with_iframe.html
new file mode 100644
index 000000000..eaddc2feb
--- /dev/null
+++ b/dom/xhr/tests/test_sync_xhr_document_write_with_iframe.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script>
+function runTest() {
+ let w = window.open('file_sync_xhr_document_write_with_iframe.html');
+ addEventListener('message', evt => {
+ is(evt.data, 'DONE');
+ w.close();
+ SimpleTest.finish();
+ }, { once: true });
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_sync_xhr_timer.xhtml b/dom/xhr/tests/test_sync_xhr_timer.xhtml
new file mode 100644
index 000000000..ce6572362
--- /dev/null
+++ b/dom/xhr/tests/test_sync_xhr_timer.xhtml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug </title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+var counter = 0;
+var xhr;
+
+function syncXHR() {
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", window.location, false);
+ xhr.send(null);
+}
+
+function increaseCounter() {
+ ++counter;
+ ok(counter <= 2, "Too many increaseCounter() calls!");
+ if (counter == 2) {
+ ok(true, "increaseCounter() should be called twice!");
+ SimpleTest.finish();
+ }
+}
+
+function runTest() {
+ setTimeout(syncXHR, 0);
+ setTimeout(increaseCounter, 0);
+ setTimeout(increaseCounter, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_sync_xhr_unload.html b/dom/xhr/tests/test_sync_xhr_unload.html
new file mode 100644
index 000000000..b11ab1f75
--- /dev/null
+++ b/dom/xhr/tests/test_sync_xhr_unload.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1307122</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="common_temporaryFileBlob.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+info("Creating the iframe...");
+var ifr = document.createElement('iframe');
+
+ifr.addEventListener("load", function ifr_load1() {
+ info("Iframe loaded");
+
+ ifr.removeEventListener("load", ifr_load1);
+ ifr.src = "empty.html";
+
+ ifr.addEventListener("load", function ifr_load2() {
+ ok(true, "Test passed");
+ SimpleTest.finish();
+ });
+
+});
+
+ifr.src = "iframe_sync_xhr_unload.html";
+document.body.appendChild(ifr);
+
+ </script>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_temporaryFileBlob.html b/dom/xhr/tests/test_temporaryFileBlob.html
new file mode 100644
index 000000000..e4db56abd
--- /dev/null
+++ b/dom/xhr/tests/test_temporaryFileBlob.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1202006</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="common_temporaryFileBlob.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+
+var tests = [
+ // from common_temporaryFileBlob.js:
+ test_simple,
+ test_reuse,
+ test_abort,
+
+ test_worker,
+ test_worker_reuse,
+ test_worker_abort,
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SpecialPowers.pushPrefEnv({ "set" : [[ "dom.blob.memoryToTemporaryFile", 1 ]] },
+ next);
+SimpleTest.waitForExplicitFinish();
+
+ </script>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_worker_terminateSyncXHR.html b/dom/xhr/tests/test_worker_terminateSyncXHR.html
new file mode 100644
index 000000000..11cea07d9
--- /dev/null
+++ b/dom/xhr/tests/test_worker_terminateSyncXHR.html
@@ -0,0 +1,45 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads XHR(Bug 450452 )
+-->
+<head>
+ <title>Test for DOM Worker Threads XHR (Bug 450452 )</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450452">DOM Worker Threads XHR (Bug 450452)</a>
+<p id="display"></p>
+<div id="content">
+ <iframe id="iframe" src="worker_terminateSyncXHR_frame.html"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ var ifr = document.getElementById("iframe");
+
+ window.onmessage = function(event) {
+ if (event.data == "TERMINATE") {
+ ok(true, "Got TERMINATE");
+ ifr.parentNode.removeChild(ifr);
+ SimpleTest.finish();
+ } else {
+ ok(false, "Unexpected message: " + event.data);
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ window.onload = function() {
+ ifr.contentWindow.doStuff();
+ }
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xhr/tests/test_worker_xhr.html b/dom/xhr/tests/test_worker_xhr.html
new file mode 100644
index 000000000..0e875b7f4
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr.html
@@ -0,0 +1,73 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads XHR(Bug 450452 )
+-->
+<head>
+ <title>Test for DOM Worker Threads XHR (Bug 450452 )</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450452">DOM Worker Threads XHR (Bug 450452)</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("xhr_worker.js");
+
+ var gotUploadLoad = false, gotLoadend = false;
+
+ worker.onmessage = function(event) {
+ is(event.target, worker);
+ var args = event.data;
+ switch (args.type) {
+ case "progress": {
+ ok(parseInt(args.current) <= parseInt(args.total));
+ } break;
+ case "error": {
+ ok(false, "XHR error: " + args.error);
+ } break;
+ case "upload.load": {
+ gotUploadLoad = true;
+ } break;
+ case "load": {
+ ok(gotUploadLoad, "Should have gotten upload load event");
+ gotLoadend = true;
+ is(args.data, "A noisy noise annoys an oyster.", "correct data");
+ document.getElementById("content").textContent = args.data;
+ } break;
+ case "loadend": {
+ ok(gotLoadend, "Should have gotten load.");
+ SimpleTest.finish();
+ break;
+ }
+ default: {
+ ok(false, "Unexpected message");
+ SimpleTest.finish();
+ }
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ ok(false, "Worker had an error:" + event.message);
+ SimpleTest.finish();
+ }
+
+ worker.postMessage("worker_testXHR.txt");
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xhr/tests/test_worker_xhr2.html b/dom/xhr/tests/test_worker_xhr2.html
new file mode 100644
index 000000000..5f1816e45
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr2.html
@@ -0,0 +1,38 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads XHR(Bug 450452 )
+-->
+<head>
+ <title>Test for DOM Worker Threads XHR (Bug 450452 )</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450452">DOM Worker Threads XHR (Bug 450452)</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("xhr2_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.data, "done", "Got correct result");
+ SimpleTest.finish();
+ }
+ worker.postMessage("worker_testXHR.txt");
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xhr/tests/test_worker_xhrAbort.html b/dom/xhr/tests/test_worker_xhrAbort.html
new file mode 100644
index 000000000..0f4b93c5a
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhrAbort.html
@@ -0,0 +1,44 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads XHR(Bug 450452 )
+-->
+<head>
+ <title>Test for DOM Worker Threads XHR (Bug 450452 )</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450452">DOM Worker Threads XHR (Bug 450452)</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script language="javascript" src="xhrAbort_worker.js"></script>
+<script class="testbody" language="javascript">
+
+ function postMessage(data) {
+ dump(data.toString() + "\n");
+
+ var worker = new Worker("xhrAbort_worker.js");
+
+ worker.onmessage = function(event) {
+ is (data.toString(), event.data.toString(), "Got different results!");
+ SimpleTest.finish();
+ };
+
+ worker.postMessage("start");
+ }
+
+ runTest();
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_worker_xhr_3rdparty.html b/dom/xhr/tests/test_worker_xhr_3rdparty.html
new file mode 100644
index 000000000..46951012a
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr_3rdparty.html
@@ -0,0 +1,74 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads XHR(Bug 450452 )
+-->
+<head>
+ <title>Test for DOM Worker Threads XHR (Bug 450452 )</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450452">DOM Worker Threads XHR (Bug 450452)</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("xhr_worker.js");
+
+ var gotUploadLoad = false, gotLoadend = false;
+
+ worker.onmessage = function(event) {
+ is(event.target, worker);
+ var args = event.data;
+ switch (args.type) {
+ case "progress": {
+ ok(parseInt(args.current) <= parseInt(args.total));
+ } break;
+ case "error": {
+ ok(false, "XHR error: " + args.error);
+ } break;
+ case "upload.load": {
+ gotUploadLoad = true;
+ } break;
+ case "load": {
+ ok(gotUploadLoad, "Should have gotten upload load event");
+ gotLoadend = true;
+ is(args.data, "a=cookie_is_set", "correct data");
+ document.getElementById("content").textContent = args.data;
+ } break;
+ case "loadend": {
+ ok(gotLoadend, "Should have gotten load.");
+ SimpleTest.finish();
+ break;
+ }
+ default: {
+ ok(false, "Unexpected message");
+ SimpleTest.finish();
+ }
+ }
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker);
+ ok(false, "Worker had an error:" + event.message);
+ SimpleTest.finish();
+ }
+
+ document.cookie = "a=cookie_is_set";
+ SpecialPowers.pushPrefEnv({ set: [[ "network.cookie.cookieBehavior", 1 ]] },
+ () => worker.postMessage("worker_file_getcookie.sjs"));
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_worker_xhr_cors_redirect.html b/dom/xhr/tests/test_worker_xhr_cors_redirect.html
new file mode 100644
index 000000000..40967d5d8
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr_cors_redirect.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1206121</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+"use strict";
+
+var worker = new Worker("worker_xhr_cors_redirect.js");
+worker.onmessage = function(e) {
+ is(e.data, 200, "We want to read 200 here.");
+ runTests();
+};
+
+var tests = [ 'http://example.com/tests/dom/xhr/tests/worker_xhr_cors_redirect.sjs',
+ 'http://example.com/tests/dom/xhr/tests/worker_xhr_cors_redirect.sjs?redirect',
+ 'worker_xhr_cors_redirect.sjs?redirect' ];
+function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ worker.postMessage(tests.shift());
+}
+
+SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+<body onload="runTests()">
+</body>
+</html>
diff --git a/dom/xhr/tests/test_worker_xhr_headers.html b/dom/xhr/tests/test_worker_xhr_headers.html
new file mode 100644
index 000000000..1416ababd
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr_headers.html
@@ -0,0 +1,86 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for XHR Headers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ </head>
+ <body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ <script class="testbody">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+var path =
+ location.pathname.substring(0, location.pathname.lastIndexOf("/") + 1);
+var filenamePrefix = "worker_xhr_headers_";
+var serverFilename = filenamePrefix + "server.sjs";
+var workerFilename = filenamePrefix + "worker.js";
+var otherHost = "example.com";
+
+info("Informing server about the current host");
+
+var xhr = new XMLHttpRequest();
+xhr.open("POST", path + serverFilename);
+xhr.setRequestHeader("options-host", otherHost);
+xhr.setRequestHeader("empty", "");
+xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ info("Launching worker");
+
+ var worker = new Worker(path + workerFilename);
+ worker.postMessage("http://" + otherHost + path + serverFilename);
+
+ worker.onmessage = function(event) {
+ ok(event.data.response === "", "Worker responded, saw no response");
+
+ var loopCount = 0;
+
+ function checkServer() {
+ var xhr2 = new XMLHttpRequest();
+ xhr2.open("GET", path + serverFilename);
+ xhr2.onreadystatechange = function() {
+ if (xhr2.readyState == 4) {
+ if (xhr2.responseText) {
+ is(xhr2.responseText,
+ "Success: expected OPTIONS request with '" +
+ event.data.header + "' header",
+ "Server saw expected requests");
+ SimpleTest.finish();
+ } else if (++loopCount < 30) {
+ setTimeout(checkServer, 1000);
+ } else {
+ ok(false, "Server never saw any requests");
+ SimpleTest.finish();
+ }
+ }
+ };
+
+ info("Checking server status (" + loopCount + ")");
+ xhr2.send();
+ }
+
+ checkServer();
+ };
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: '" + event.message + "'");
+ event.preventDefault();
+ SimpleTest.finish();
+ };
+ }
+};
+xhr.send();
+
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/xhr/tests/test_worker_xhr_implicit_cancel.html b/dom/xhr/tests/test_worker_xhr_implicit_cancel.html
new file mode 100644
index 000000000..a9efb14b9
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr_implicit_cancel.html
@@ -0,0 +1,44 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads XHR(Bug 450452 )
+-->
+<head>
+ <title>Test for DOM Worker Threads XHR (Bug 450452 )</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450452">DOM Worker Threads XHR (Bug 450452)</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var worker = new Worker("xhr_implicit_cancel_worker.js");
+
+ worker.onmessage = function(event) {
+ is(event.target, worker, "Expected event target for message");
+ ok(true, "Worker didn't have an error");
+ SimpleTest.finish();
+ };
+
+ worker.onerror = function(event) {
+ is(event.target, worker, "Expected event target for error");
+ ok(false, "Worker had an error:" + event.message);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xhr/tests/test_worker_xhr_parameters.html b/dom/xhr/tests/test_worker_xhr_parameters.html
new file mode 100644
index 000000000..ae880aabb
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr_parameters.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for XMLHttpRequest with system privileges</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+function message(event) {
+ if (event.data.test == 'ok')
+ ok(event.data.a, event.data.event);
+ else if(event.data.test == 'is')
+ is(event.data.a, event.data.b, event.data.event);
+ else if(event.data.test == 'finish') {
+ run();
+ }
+};
+
+function test1() {
+ var worker = new Worker("test_worker_xhr_parameters.js");
+ worker.onmessage = message;
+
+ // Run the tests once without API privileges...
+ worker.postMessage(false);
+}
+
+function test2() {
+ // ...and once with privileges.
+ SpecialPowers.pushPermissions([{type: "systemXHR", allow: true, context: document}],
+ function () {
+ var worker = new Worker("test_worker_xhr_parameters.js");
+ worker.onmessage = message;
+ worker.postMessage(true);
+ }
+ );
+}
+
+var tests = [ test1, test2 ];
+function run() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var func = tests.shift();
+ func();
+}
+
+SimpleTest.waitForExplicitFinish();
+run();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_worker_xhr_parameters.js b/dom/xhr/tests/test_worker_xhr_parameters.js
new file mode 100644
index 000000000..80db35a43
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr_parameters.js
@@ -0,0 +1,75 @@
+function ok(what, msg) {
+ postMessage({ event: msg, test: 'ok', a: what });
+}
+
+function is(a, b, msg) {
+ postMessage({ event: msg, test: 'is', a: a, b: b });
+}
+
+// This is a copy of dom/xhr/tests/test_XHR_parameters.js
+var validParameters = [
+ undefined,
+ null,
+ {},
+ {mozSystem: ""},
+ {mozSystem: 0},
+ {mozAnon: 1},
+ {mozAnon: []},
+ {get mozAnon() { return true; }},
+ 0,
+ 7,
+ Math.PI,
+ "string",
+ true,
+ false,
+];
+
+var invalidParameters = [
+ {get mozSystem() { throw "Bla"; } },
+];
+
+
+function testParameters(havePrivileges) {
+
+ function testValidParameter(value) {
+ var xhr;
+ try {
+ xhr = new XMLHttpRequest(value);
+ } catch (ex) {
+ ok(false, "Got unexpected exception: " + ex);
+ return;
+ }
+ ok(!!xhr, "passed " + JSON.stringify(value));
+
+ // If the page doesnt have privileges to create a system or anon XHR,
+ // these flags will always be false no matter what is passed.
+ var expectedAnon = false;
+ var expectedSystem = false;
+ if (havePrivileges) {
+ expectedAnon = Boolean(value && value.mozAnon);
+ expectedSystem = Boolean(value && value.mozSystem);
+ }
+ is(xhr.mozAnon, expectedAnon, "testing mozAnon");
+ is(xhr.mozSystem, expectedSystem, "testing mozSystem");
+ }
+
+
+ function testInvalidParameter(value) {
+ try {
+ new XMLHttpRequest(value);
+ ok(false, "invalid parameter did not cause exception: " +
+ JSON.stringify(value));
+ } catch (ex) {
+ ok(true, "invalid parameter raised exception as expected: " +
+ JSON.stringify(ex));
+ }
+ }
+
+ validParameters.forEach(testValidParameter);
+ invalidParameters.forEach(testInvalidParameter);
+}
+
+self.onmessage = function onmessage(event) {
+ testParameters(event.data);
+ postMessage({test: "finish"});
+};
diff --git a/dom/xhr/tests/test_worker_xhr_responseURL.html b/dom/xhr/tests/test_worker_xhr_responseURL.html
new file mode 100644
index 000000000..424d0b95d
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr_responseURL.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=998076
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 998076</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+/** Test for Bug 998076 **/
+"use strict";
+
+var worker = new Worker("../../../dom/xhr/tests/file_XHRResponseURL.js");
+
+var requestObserver = {
+ observe: function (aSubject, aTopic, aData) {
+ worker.postMessage("request");
+ }
+};
+
+worker.addEventListener("message", function (aEvent) {
+ var data = aEvent.data;
+ if (data == "done") {
+ SimpleTest.finish();
+ return;
+ }
+ if (data == "start") {
+ return;
+ }
+ if (data.type == "is") {
+ SimpleTest.is(data.actual, data.expected, data.message);
+ worker.postMessage("pong");
+ return;
+ }
+ if (data.type == "ok") {
+ SimpleTest.ok(data.bool, data.message);
+ worker.postMessage("pong");
+ return;
+ }
+ if (data.type == "info") {
+ SimpleTest.info(data.message);
+ worker.postMessage("pong");
+ return;
+ }
+ if (data.type === "redirect_test") {
+ if (data.status === "start") {
+ SpecialPowers.addObserver(requestObserver, "specialpowers-http-notify-request", false);
+ return;
+ }
+ if (data.status === "end") {
+ SpecialPowers.removeObserver(requestObserver, "specialpowers-http-notify-request");
+ return;
+ }
+ }
+});
+
+function runTests() {
+ SimpleTest.waitForExplicitFinish();
+ worker.postMessage("start");
+}
+
+ </script>
+</head>
+<body onload="runTests()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=998076">Mozilla Bug 998076</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_worker_xhr_system.html b/dom/xhr/tests/test_worker_xhr_system.html
new file mode 100644
index 000000000..6de0016e2
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr_system.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for XMLHttpRequest with system privileges</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+function message(event) {
+ if (event.data.test == 'ok')
+ ok(event.data.a, event.data.event);
+ else if(event.data.test == 'is')
+ is(event.data.a, event.data.b, event.data.event);
+ else if(event.data.test == 'finish') {
+ run();
+ }
+};
+
+function test1() {
+ var worker = new Worker("test_worker_xhr_system.js");
+ worker.onmessage = message;
+ worker.postMessage(true);
+}
+
+var tests = [ test1 ];
+function run() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var func = tests.shift();
+ func();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], run);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_worker_xhr_system.js b/dom/xhr/tests/test_worker_xhr_system.js
new file mode 100644
index 000000000..5ad63225c
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr_system.js
@@ -0,0 +1,30 @@
+function ok(what, msg) {
+ postMessage({ event: msg, test: 'ok', a: what });
+}
+
+function is(a, b, msg) {
+ postMessage({ event: msg, test: 'is', a: a, b: b });
+}
+
+self.onmessage = function onmessage(event) {
+
+ // An XHR with system privileges will be able to do cross-site calls.
+
+ const TEST_URL = "http://example.com/tests/dom/xhr/tests/test_XHR_system.html";
+ is(location.hostname, "mochi.test", "hostname should be mochi.test");
+
+ var xhr = new XMLHttpRequest({mozSystem: true});
+ is(xhr.mozSystem, true, ".mozSystem == true");
+ xhr.open("GET", TEST_URL);
+ xhr.onload = function onload() {
+ is(xhr.status, 200);
+ ok(xhr.responseText != null);
+ ok(xhr.responseText.length);
+ postMessage({test: "finish"});
+ };
+ xhr.onerror = function onerror() {
+ ok(false, "Got an error event!");
+ postMessage({test: "finish"});
+ }
+ xhr.send();
+};
diff --git a/dom/xhr/tests/test_worker_xhr_timeout.html b/dom/xhr/tests/test_worker_xhr_timeout.html
new file mode 100644
index 000000000..d9bb6a7ae
--- /dev/null
+++ b/dom/xhr/tests/test_worker_xhr_timeout.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=498998
+-->
+<head>
+ <title>Test for Bug 498998</title>
+ <script type="application/javascript"
+ src="/MochiKit/MochiKit.js"></script>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet"
+ type="text/css"
+ href="/tests/SimpleTest/test.css">
+</head>
+<body>
+
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=498998"
+ >Mozilla Bug 498998 (Worker XMLHttpRequest timeout)</a>
+<p id="display"></p>
+<div id="content">
+ This test takes over 1 minute to run, probably over 2 minutes.
+</div>
+<pre id="test">
+<script type="text/javascript">
+ var worker = new Worker("../../../dom/xhr/tests/test_XHR_timeout.js");
+
+ worker.addEventListener("message", function (event) {
+ if (event.data == "done") {
+ SimpleTest.finish();
+ return;
+ }
+ if (event.data == "start") {
+ return;
+ }
+ if (event.data.type == "is") {
+ SimpleTest.is(event.data.got, event.data.expected, event.data.msg);
+ return;
+ }
+ if (event.data.type == "ok") {
+ SimpleTest.ok(event.data.bool, event.data.msg);
+ return;
+ }
+ });
+ // Final test harness setup and launch.
+ (function() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestLongerTimeout(20);
+ var msg = "This test will take approximately " + (20 * 10)
+ msg += " seconds to complete, at most.";
+ document.getElementById("content").firstChild.nodeValue = msg;
+ worker.postMessage("start");
+ })();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_xhr_abort_after_load.html b/dom/xhr/tests/test_xhr_abort_after_load.html
new file mode 100644
index 000000000..780c11253
--- /dev/null
+++ b/dom/xhr/tests/test_xhr_abort_after_load.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test bug 482935</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href=" /tests/SimpleTest/test.css" />
+</head>
+<body onload="onWindowLoad()">
+<script class="testbody" type="text/javascript">"use strict";
+SimpleTest.waitForExplicitFinish();
+
+var url = "file_XHR_pass1.xml";
+
+function onWindowLoad() {
+ runTest();
+}
+
+function runTest() {
+ var testFunctions = [
+ startTest1,
+ startTest2,
+ startTest3,
+ ];
+
+ function nextTest() {
+ if (testFunctions.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ (testFunctions.shift())();
+ }
+
+ nextTest();
+
+ var xhr;
+ function startTest1() {
+ xhr = new XMLHttpRequest();
+ xhr.onload = onLoad1;
+ xhr.open("GET", url);
+ xhr.send();
+ }
+
+ function onLoad1() {
+ is(xhr.readyState, xhr.DONE, "readyState should be DONE");
+ xhr.onabort = onAbort1;
+ xhr.abort();
+
+ function onAbort1(e) {
+ ok(false, e.type + " event should not be fired!");
+ }
+
+ is(xhr.readyState, xhr.UNSENT, "readyState should be UNSENT");
+ nextTest();
+ }
+
+ function startTest2() {
+ xhr = new XMLHttpRequest();
+ xhr.onloadstart = onAfterSend;
+ xhr.open("GET", url);
+ xhr.send();
+ }
+
+ function startTest3() {
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", url);
+ xhr.send();
+ onAfterSend();
+ }
+
+ function onAfterSend() {
+ is(xhr.readyState, xhr.OPENED, "readyState should be OPENED");
+ var sent = false;
+ try {
+ xhr.send();
+ } catch (e) {
+ sent = true;
+ }
+ ok(sent, "send() flag should be set");
+ var aborted = false;
+ xhr.onabort = onAbort2;
+ xhr.abort();
+
+ function onAbort2() {
+ is(xhr.readyState, xhr.DONE, "readyState should be DONE");
+ aborted = true;
+ }
+
+ ok(aborted, "abort event should be fired");
+ is(xhr.readyState, xhr.UNSENT, "readyState should be UNSENT");
+ nextTest();
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_xhr_forbidden_headers.html b/dom/xhr/tests/test_xhr_forbidden_headers.html
new file mode 100644
index 000000000..fc076731e
--- /dev/null
+++ b/dom/xhr/tests/test_xhr_forbidden_headers.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=308484
+-->
+<head>
+ <title>Test for Bug 308484</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=308484">Mozilla Bug 308484</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 308484 **/
+
+var headers = [
+ "aCCept-chaRset",
+ "acCePt-eNcoDing",
+ "aCcEsS-cOnTrOl-ReQuEsT-mEtHoD",
+ "aCcEsS-cOnTrOl-ReQuEsT-hEaDeRs",
+ "coNnEctIon",
+ "coNtEnt-LEngth",
+ "CoOKIe",
+ "cOOkiE2",
+ "DATE",
+ "dNT",
+ "exPeCt",
+ "hOSt",
+ "keep-alive",
+ "oRiGiN",
+ "reFERer",
+ "te",
+ "trAiLer",
+ "trANsfEr-eNcoDiNg",
+ "uPGraDe",
+ "viA",
+ "pRoxy-",
+ "sEc-",
+ "proxy-fOobar",
+ "sec-bAZbOx"
+];
+var i, request;
+
+function startTest() {
+ // Try setting headers in unprivileged context
+ request = new XMLHttpRequest();
+ request.open("GET", window.location.href);
+ for (i = 0; i < headers.length; i++)
+ request.setRequestHeader(headers[i], "test" + i);
+ request.send(); // headers aren't set on the channel until send()
+
+ // Read out headers
+ var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
+ for (i = 0; i < headers.length; i++) {
+ // Retrieving Content-Length will throw an exception
+ var value = null;
+ try {
+ value = channel.getRequestHeader(headers[i]);
+ }
+ catch(e) {}
+
+ isnot(value, "test" + i, "Setting " + headers[i] + " header in unprivileged context");
+ }
+
+ // Try setting headers in privileged context
+ request = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ request.open("GET", window.location.href);
+ for (i = 0; i < headers.length; i++)
+ request.setRequestHeader(headers[i], "test" + i);
+ request.send(); // headers aren't set on the channel until send()
+
+ // Read out headers
+ var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
+ for (i = 0; i < headers.length; i++) {
+ var value = channel.getRequestHeader(headers[i]);
+ is(value, "test" + i, "Setting " + headers[i] + " header in privileged context");
+ }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], startTest);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_xhr_overridemimetype_throws_on_invalid_state.html b/dom/xhr/tests/test_xhr_overridemimetype_throws_on_invalid_state.html
new file mode 100644
index 000000000..bdbbf1591
--- /dev/null
+++ b/dom/xhr/tests/test_xhr_overridemimetype_throws_on_invalid_state.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test bug 482935</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href=" /tests/SimpleTest/test.css" />
+</head>
+<body onload="onWindowLoad()">
+<script class="testbody" type="text/javascript">"use strict";
+SimpleTest.waitForExplicitFinish();
+
+var url = "file_XHR_pass1.xml";
+
+function onWindowLoad() {
+ runTest();
+}
+
+function runTest() {
+ var testFunctions = [
+ function() { testOverMimeTypeThrowsDuringReadyState(3, "application/xml"); },
+ function() { testOverMimeTypeThrowsDuringReadyState(3, "application/xml;charset=Shift-JIS"); },
+ function() { testOverMimeTypeThrowsDuringReadyState(4, "application/xml"); },
+ function() { testOverMimeTypeThrowsDuringReadyState(4, "application/xml;charset=Shift-JIS"); },
+ ];
+
+ function nextTest() {
+ if (testFunctions.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ (testFunctions.shift())();
+ }
+
+ nextTest();
+
+ function testOverMimeTypeThrowsDuringReadyState(readyState, mimeType) {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === readyState) {
+ try {
+ xhr.overrideMimeType(mimeType);
+ ok(false, "No exception thrown, but expected InvalidStateError" +
+ " for readyState=" + readyState + ", mimeType=" + mimeType);
+ } catch(exc) {
+ is(exc.name, "InvalidStateError", "Expected InvalidStateError, got " + exc.name +
+ " for readyState=" + readyState + ", mimeType=" + mimeType);
+ }
+ }
+ if (xhr.readyState === 4) {
+ is(xhr.responseXML, null, "responseXML was not null" +
+ " for readyState=" + readyState + ", mimeType=" + mimeType);
+ nextTest();
+ }
+ }
+ xhr.open("GET", url);
+ xhr.send();
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_xhr_progressevents.html b/dom/xhr/tests/test_xhr_progressevents.html
new file mode 100644
index 000000000..f22927daf
--- /dev/null
+++ b/dom/xhr/tests/test_xhr_progressevents.html
@@ -0,0 +1,333 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for XMLHttpRequest Progress Events</title>
+ <script type="text/javascript" src="/MochiKit/packed.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="gen.next();">
+<pre id=l></pre>
+<script type="application/javascript;version=1.7">
+SimpleTest.waitForExplicitFinish();
+
+var gen = runTests();
+
+function log(s) {
+ // Uncomment these to get debugging information
+ /*
+ document.getElementById("l").textContent += s + "\n";
+ dump(s + "\n");
+ */
+}
+
+function getEvent(e) {
+ log("got event: " + e.type + " (" + e.target.readyState + ")");
+ gen.send(e);
+}
+
+function startsWith(a, b) {
+ return a.substr(0, b.length) === b;
+}
+
+function updateProgress(e, data, testName) {
+ var test = " while running " + testName;
+ is(e.type, "progress", "event type" + test);
+
+ let response;
+ if (data.nodata) {
+ is(e.target.response, null, "response should be null" + test);
+ response = null;
+ }
+ else if (data.text) {
+ is(typeof e.target.response, "string", "response should be a string" + test);
+ response = e.target.response;
+ }
+ else if (data.blob) {
+ ok(e.target.response instanceof Blob, "response should be a Blob" + test);
+ response = e.target.response;
+ }
+ else {
+ ok(e.target.response instanceof ArrayBuffer, "response should be an ArrayBuffer" + test);
+ response = bufferToString(e.target.response);
+ }
+ is(e.target.response, e.target.response, "reflexivity should hold" + test);
+
+ if (!data.nodata && !data.encoded) {
+ if (data.blob) {
+ is(e.loaded, response.size, "event.loaded matches response size" + test);
+ }
+ else if (!data.chunked) {
+ is(e.loaded, response.length, "event.loaded matches response size" + test);
+ }
+ else {
+ is(e.loaded - data.receivedBytes, response.length,
+ "event.loaded grew by response size" + test);
+ }
+ }
+ ok(e.loaded > data.receivedBytes, "event.loaded increased" + test);
+ ok(e.loaded - data.receivedBytes <= data.pendingBytes,
+ "event.loaded didn't increase too much" + test);
+
+ if (!data.nodata && !data.blob) {
+ var newData;
+ ok(startsWith(response, data.receivedResult),
+ "response strictly grew" + test);
+ newData = response.substr(data.receivedResult.length);
+
+ if (!data.encoded) {
+ ok(newData.length > 0, "sanity check for progress" + test);
+ }
+ ok(startsWith(data.pendingResult, newData), "new data matches expected" + test);
+ }
+
+ is(e.lengthComputable, "total" in data, "lengthComputable" + test);
+ if ("total" in data) {
+ is(e.total, data.total, "total" + test);
+ }
+
+ if (!data.nodata && !data.blob) {
+ data.pendingResult = data.pendingResult.substr(newData.length);
+ }
+ data.pendingBytes -= e.loaded - data.receivedBytes;
+ data.receivedResult = response;
+ data.receivedBytes = e.loaded;
+}
+
+function sendData(s) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", "progressserver.sjs?send");
+ // The Blob constructor encodes String elements as UTF-8;
+ // for straight bytes, manually convert to ArrayBuffer first
+ var buffer = new Uint8Array(s.length);
+ for (var i = 0; i < s.length; ++i) {
+ buffer[i] = s.charCodeAt(i) & 0xff;
+ };
+ xhr.send(new Blob([buffer]));
+}
+
+function closeConn() {
+ log("in closeConn");
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", "progressserver.sjs?close");
+ xhr.send();
+ return xhr;
+}
+
+var longString = "long";
+while(longString.length < 65536)
+ longString += longString;
+
+function utf8encode(s) {
+ return unescape(encodeURIComponent(s));
+}
+
+function bufferToString(buffer) {
+ return String.fromCharCode.apply(String, new Uint8Array(buffer));
+}
+
+function runTests() {
+ var xhr = new XMLHttpRequest();
+ xhr.onprogress = xhr.onload = xhr.onerror = xhr.onreadystatechange = xhr.onloadend = getEvent;
+
+ var responseTypes = [{ type: "text", text: true },
+ { type: "arraybuffer", text: false, nodata: true },
+ { type: "blob", text: false, nodata: true, blob: true },
+ { type: "moz-blob", text: false, nodata: false, blob: true },
+ { type: "document", text: true, nodata: true },
+ { type: "json", text: true, nodata: true },
+ { type: "", text: true },
+ { type: "moz-chunked-text", text: true, chunked: true },
+ { type: "moz-chunked-arraybuffer", text: false, chunked: true },
+ ];
+ var responseType;
+ var fileExpectedResult = "";
+ for (var i = 0; i < 65536; i++) {
+ fileExpectedResult += String.fromCharCode(i & 255);
+ }
+ while (responseType = responseTypes.shift()) {
+ let tests = [{ open: "Content-Type=text/plain", name: "simple test" },
+ { data: "hello world" },
+ { data: "\u0000\u0001\u0002\u0003" },
+ { data: longString },
+ { data: "x" },
+ { close: true },
+ { open: "Content-Type=text/plain&Content-Length=20", name: "with length", total: 20 },
+ // 5 bytes from the "ready" in the open step
+ { data: "abcde" },
+ { data: "0123456789" },
+ { close: true },
+ { open: "Content-Type=application/xml", name: "without length, as xml" },
+ { data: "<out>" },
+ { data: "text" },
+ { data: "</foo>invalid" },
+ { close: true },
+ { open: "Content-Type=text/plain;charset%3dutf-8", name: "utf8 data", encoded: true },
+ { data: utf8encode("räksmörgås"), utf16: "räksmörgås" },
+ { data: utf8encode("Ã…").substr(0,1), utf16: "" },
+ { data: utf8encode("Ã…").substr(1), utf16: "Ã…" },
+ { data: utf8encode("aöb").substr(0,2), utf16: "a" },
+ { data: utf8encode("aöb").substr(2), utf16: "öb" },
+ { data: utf8encode("a\u867Eb").substr(0,3), utf16: "a" },
+ { data: utf8encode("a\u867Eb").substr(3,1), utf16: "\u867E" },
+ { data: utf8encode("a\u867Eb").substr(4), utf16: "b" },
+ { close: true },
+ ];
+ if (responseType.blob) {
+ tests.push({ file: "file_XHR_binary2.bin", name: "cacheable data", total: 65536 },
+ { close: true },
+ { file: "file_XHR_binary2.bin", name: "cached data", total: 65536 },
+ { close: true });
+ }
+ let testState = { index: 0 };
+
+ for (let i = 0; i < tests.length; ++i) {
+ let test = tests[i];
+ testState.index++;
+ if ("open" in test || "file" in test) {
+ log("opening " + testState.name);
+ testState = { name: test.name + " for " + responseType.type,
+ index: 0,
+ pendingResult: "ready",
+ pendingBytes: 5,
+ receivedResult: "",
+ receivedBytes: 0,
+ total: test.total,
+ encoded: test.encoded,
+ nodata: responseType.nodata,
+ chunked: responseType.chunked,
+ text: responseType.text,
+ blob: responseType.blob,
+ file: test.file };
+
+ xhr.onreadystatechange = null;
+ if (testState.file)
+ xhr.open("GET", test.file);
+ else
+ xhr.open("POST", "progressserver.sjs?open&" + test.open);
+ xhr.responseType = responseType.type;
+ xhr.send("ready");
+ xhr.onreadystatechange = getEvent;
+
+ let e = yield undefined;
+ is(e.type, "readystatechange", "should readystate to headers-received starting " + testState.name);
+ is(xhr.readyState, xhr.HEADERS_RECEIVED, "should be in state HEADERS_RECEIVED starting " + testState.name);
+
+ e = yield undefined;
+ is(e.type, "readystatechange", "should readystate to loading starting " + testState.name);
+ is(xhr.readyState, xhr.LOADING, "should be in state LOADING starting " + testState.name);
+ if (typeof testState.total == "undefined")
+ delete testState.total;
+ }
+ if ("file" in test) {
+ testState.pendingBytes = testState.total;
+ testState.pendingResult = fileExpectedResult;
+ }
+ if ("close" in test) {
+ log("closing");
+ let xhrClose;
+ if (!testState.file)
+ xhrClose = closeConn();
+
+ e = yield undefined;
+ is(e.type, "readystatechange", "should readystate to done closing " + testState.name);
+ is(xhr.readyState, xhr.DONE, "should be in state DONE closing " + testState.name);
+ log("readystate to 4");
+
+ if (responseType.chunked) {
+ xhr.responseType;
+ is(xhr.response, null, "chunked data has null response for " + testState.name);
+ }
+
+ e = yield undefined;
+ is(e.type, "load", "should fire load closing " + testState.name);
+ is(e.lengthComputable, e.total != 0, "length should " + (e.total == 0 ? "not " : "") + "be computable during load closing " + testState.name);
+ log("got load");
+
+ if (responseType.chunked) {
+ is(xhr.response, null, "chunked data has null response for " + testState.name);
+ }
+
+ e = yield undefined;
+ is(e.type, "loadend", "should fire loadend closing " + testState.name);
+ is(e.lengthComputable, e.total != 0, "length should " + (e.total == 0 ? "not " : "") + "be computable during loadend closing " + testState.name);
+ log("got loadend");
+
+ // if we closed the connection using an explicit request, make sure that goes through before
+ // running the next test in order to avoid reordered requests from closing the wrong
+ // connection.
+ if (xhrClose && xhrClose.readyState != xhrClose.DONE) {
+ log("wait for closeConn to finish");
+ xhrClose.onloadend = getEvent;
+ yield undefined;
+ is(xhrClose.readyState, xhrClose.DONE, "closeConn finished");
+ }
+
+ if (responseType.chunked) {
+ is(xhr.response, null, "chunked data has null response for " + testState.name);
+ }
+
+ if (!testState.nodata && !responseType.blob || responseType.chunked) {
+ // This branch intentionally left blank
+ // Under these conditions we check the response during updateProgress
+ }
+ else if (responseType.type === "arraybuffer") {
+ is(bufferToString(xhr.response), testState.pendingResult,
+ "full response for " + testState.name);
+ }
+ else if (responseType.blob) {
+ let reader = new FileReader;
+ reader.readAsBinaryString(xhr.response);
+ reader.onloadend = getEvent;
+ yield undefined;
+
+ is(reader.result, testState.pendingResult,
+ "full response in blob for " + testState.name);
+ }
+
+ testState.name = "";
+ }
+ if ("data" in test) {
+ log("sending");
+ if (responseType.text) {
+ testState.pendingResult += "utf16" in test ? test.utf16 : test.data;
+ }
+ else {
+ testState.pendingResult += test.data;
+ }
+ testState.pendingBytes = test.data.length;
+ sendData(test.data);
+ }
+
+ while(testState.pendingBytes) {
+ log("waiting for more bytes: " + testState.pendingBytes);
+ e = yield undefined;
+ // Readystate can fire several times between each progress event.
+ if (e.type === "readystatechange")
+ continue;
+
+ updateProgress(e, testState, "data for " + testState.name + "[" + testState.index + "]");
+ if (responseType.chunked) {
+ testState.receivedResult = "";
+ }
+ }
+
+ if (!testState.nodata && !testState.blob) {
+ is(testState.pendingResult, "",
+ "should have consumed the expected result");
+ }
+
+ log("done with this test");
+ }
+
+ is(testState.name, "", "forgot to close last test");
+ }
+
+ SimpleTest.finish();
+ yield undefined;
+}
+
+</script>
+
+</body>
+</html>
diff --git a/dom/xhr/tests/test_xhr_send.html b/dom/xhr/tests/test_xhr_send.html
new file mode 100644
index 000000000..6633dcb15
--- /dev/null
+++ b/dom/xhr/tests/test_xhr_send.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1096263
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1096263</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1096263 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function simpleGetTest() {
+ var x = new XMLHttpRequest();
+ x.open("GET", "echo.sjs");
+ x.onload = function() {
+ ok(true, "Should have processed GET");
+ simplePostTest();
+ }
+ x.send({});
+}
+
+function simplePostTest() {
+ var x = new XMLHttpRequest();
+ x.open("POST", "echo.sjs");
+ x.onload = function() {
+ is(x.responseText, "somedata", "Should have processed POST");
+ undefinedPostTest();
+ }
+ x.send({toString: function() { return "somedata"; }});
+}
+
+function undefinedPostTest() {
+ var x = new XMLHttpRequest();
+ x.open("POST", "echo.sjs");
+ x.onload = function() {
+ is(x.responseText, "undefined", "Should have processed POST");
+ nullPostTest();
+ }
+ x.send({toString: function() { return undefined; }});
+}
+
+function nullPostTest() {
+ var x = new XMLHttpRequest();
+ x.open("POST", "echo.sjs");
+ x.onload = function() {
+ is(x.responseText, "null", "Should have processed POST");
+ testExceptionInToString();
+ }
+ x.send({toString: function() { return null; }});
+}
+
+function testExceptionInToString() {
+ var x = new XMLHttpRequest();
+ x.open("GET", "echo.sjs");
+ x.onload = function() {
+ ok(false);
+ SimpleTest.finish();
+ }
+ try {
+ x.send({toString: function() { throw "dummy"; }});
+ } catch(ex) {
+ is(ex, "dummy");
+ SimpleTest.finish();
+ }
+}
+
+ </script>
+</head>
+<body onload="simpleGetTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1096263">Mozilla Bug 1096263</a>
+<p id="display"></p>
+<div id="content">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_xhr_send_readystate.html b/dom/xhr/tests/test_xhr_send_readystate.html
new file mode 100644
index 000000000..1c86681de
--- /dev/null
+++ b/dom/xhr/tests/test_xhr_send_readystate.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=814064
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 814064</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=814064">Mozilla Bug 814064</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 814064 **/
+var xhr = new XMLHttpRequest();
+var firings = 0;
+function readyStateHandler() {
+ is(xhr.readyState, XMLHttpRequest.OPENED, "Should be in OPENED state");
+ ++firings;
+}
+xhr.onreadystatechange = readyStateHandler;
+xhr.open("GET", "");
+is(firings, 1, "Should have fired the readystatechange handler");
+xhr.send();
+is(firings, 1, "Should not have fired the readystatechange handler");
+xhr.onreadystatechange = null;
+xhr.abort();
+is(firings, 1, "Should not have fired the handler no-longer-registered handler");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/test_xhr_withCredentials.html b/dom/xhr/tests/test_xhr_withCredentials.html
new file mode 100644
index 000000000..71d7dcccf
--- /dev/null
+++ b/dom/xhr/tests/test_xhr_withCredentials.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=814050
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 814050</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=814050">Mozilla Bug 814050</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 814050, but modified now that the spec has changed **/
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "", false);
+xhr.withCredentials = true;
+ok(true, "Should not throw on withCredentials sets for sync XHR");
+
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "", true);
+xhr.withCredentials = true;
+ok(true, "Should not throw on withCredentials sets for async XHR");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xhr/tests/worker_bug1300552.js b/dom/xhr/tests/worker_bug1300552.js
new file mode 100644
index 000000000..fbc58b880
--- /dev/null
+++ b/dom/xhr/tests/worker_bug1300552.js
@@ -0,0 +1,33 @@
+function info(msg) {
+ postMessage({type: 'info', msg: msg});
+}
+
+function ok(a, msg) {
+ postMessage({type: 'check', what: !!a, msg: msg});
+}
+
+function finish() {
+ postMessage({type: 'finish'});
+}
+
+info("Creating XHR...");
+var xhr = new XMLHttpRequest();
+xhr.open("POST", "echo.sjs");
+xhr.responseType = 'arraybuffer';
+
+info("Sending some data...");
+var data = new Array(256).join("1234567890ABCDEF");
+xhr.send({toString: function() { return data; }});
+
+var aborted = false;
+
+xhr.onprogress = function() {
+ info("Onprogress, we abort!");
+ aborted = true;
+ xhr.abort();
+}
+
+xhr.onloadend = function() {
+ ok(aborted, "We are still alive after an abort()!");
+ finish();
+}
diff --git a/dom/xhr/tests/worker_file_getcookie.sjs b/dom/xhr/tests/worker_file_getcookie.sjs
new file mode 100644
index 000000000..b5204bdd7
--- /dev/null
+++ b/dom/xhr/tests/worker_file_getcookie.sjs
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+function handleRequest(request, response) {
+ try {
+ var cookie = request.getHeader("Cookie");
+ } catch (e) {
+ cookie = "EMPTY_COOKIE";
+ }
+
+ // avoid confusing cache behaviors.
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-type", "text/plain", false);
+ response.setStatusLine(request.httpVersion, "200", "OK");
+ response.write(cookie);
+}
diff --git a/dom/xhr/tests/worker_temporaryFileBlob.js b/dom/xhr/tests/worker_temporaryFileBlob.js
new file mode 100644
index 000000000..db40f72a8
--- /dev/null
+++ b/dom/xhr/tests/worker_temporaryFileBlob.js
@@ -0,0 +1,29 @@
+importScripts('common_temporaryFileBlob.js');
+
+function info(msg) {
+ postMessage({type: 'info', msg: msg});
+}
+
+function ok(a, msg) {
+ postMessage({type: 'check', what: !!a, msg: msg});
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function next() {
+ postMessage({type: 'finish'});
+}
+
+onmessage = function(e) {
+ if (e.data == 'simple') {
+ test_simple();
+ } else if (e.data == 'abort') {
+ test_abort();
+ } else if (e.data == 'reuse') {
+ test_reuse();
+ } else {
+ ok(false, 'Something wrong happened');
+ }
+}
diff --git a/dom/xhr/tests/worker_terminateSyncXHR_frame.html b/dom/xhr/tests/worker_terminateSyncXHR_frame.html
new file mode 100644
index 000000000..04bd53ff1
--- /dev/null
+++ b/dom/xhr/tests/worker_terminateSyncXHR_frame.html
@@ -0,0 +1,25 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for SharedWorker</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ function doStuff() {
+ var worker = new Worker("terminateSyncXHR_worker.js");
+
+ worker.onmessage = function(event) {
+ parent.postMessage(event.data, "*");
+ };
+
+ worker.onerror = function(event) {
+ parent.postMessage("ERROR!", "*");
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/xhr/tests/worker_testXHR.txt b/dom/xhr/tests/worker_testXHR.txt
new file mode 100644
index 000000000..2beab22c6
--- /dev/null
+++ b/dom/xhr/tests/worker_testXHR.txt
@@ -0,0 +1 @@
+A noisy noise annoys an oyster. \ No newline at end of file
diff --git a/dom/xhr/tests/worker_xhr_cors_redirect.js b/dom/xhr/tests/worker_xhr_cors_redirect.js
new file mode 100644
index 000000000..c006b4a2e
--- /dev/null
+++ b/dom/xhr/tests/worker_xhr_cors_redirect.js
@@ -0,0 +1,10 @@
+onmessage = function(e) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', e.data, true);
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ postMessage(xhr.status);
+ }
+ };
+ xhr.send();
+};
diff --git a/dom/xhr/tests/worker_xhr_cors_redirect.sjs b/dom/xhr/tests/worker_xhr_cors_redirect.sjs
new file mode 100644
index 000000000..07e8ed5dc
--- /dev/null
+++ b/dom/xhr/tests/worker_xhr_cors_redirect.sjs
@@ -0,0 +1,10 @@
+function handleRequest(request, response) {
+ response.setHeader("Access-Control-Allow-Origin", "*");
+
+ if (request.queryString == 'redirect') {
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", "worker_xhr_cors_redirect.sjs");
+ } else {
+ response.write("'hello world'");
+ }
+}
diff --git a/dom/xhr/tests/worker_xhr_headers_server.sjs b/dom/xhr/tests/worker_xhr_headers_server.sjs
new file mode 100644
index 000000000..624abb9fc
--- /dev/null
+++ b/dom/xhr/tests/worker_xhr_headers_server.sjs
@@ -0,0 +1,66 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+function handleRequest(request, response) {
+ switch (request.method) {
+ case "POST":
+ try {
+ var optionsHost = request.getHeader("options-host");
+ } catch(e) { }
+
+ var headerFound = false;
+ if (optionsHost) {
+ setState("postHost", request.host);
+ setState("optionsHost", optionsHost);
+ headerFound = true;
+ }
+
+ try {
+ var emptyHeader = "nada" + request.getHeader("empty");
+ } catch(e) { }
+
+ if (emptyHeader && emptyHeader == "nada") {
+ setState("emptyHeader", "nada");
+ headerFound = true;
+ }
+ if (headerFound) {
+ return;
+ } else {
+ break;
+ }
+
+ case "OPTIONS":
+ if (getState("optionsHost") == request.host) {
+ try {
+ var optionsHeader =
+ request.getHeader("Access-Control-Request-Headers");
+ } catch(e) { }
+ setState("optionsHeader", "'" + optionsHeader + "'");
+ }
+ break;
+
+ case "GET":
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+
+ if (getState("postHost") == request.host &&
+ getState("emptyHeader") == "nada") {
+ var result = getState("optionsHeader");
+ if (result) {
+ response.write("Success: expected OPTIONS request with " + result +
+ " header");
+ } else if (getState("badGet") == 1) {
+ response.write("Error: unexpected GET request");
+ }
+ } else {
+ setState("badGet", "1");
+ response.write("Error: this response should never be seen");
+ }
+ return;
+ }
+
+ response.setStatusLine(request.httpVersion, 501, "Not Implemented");
+}
diff --git a/dom/xhr/tests/worker_xhr_headers_worker.js b/dom/xhr/tests/worker_xhr_headers_worker.js
new file mode 100644
index 000000000..af88b83ca
--- /dev/null
+++ b/dom/xhr/tests/worker_xhr_headers_worker.js
@@ -0,0 +1,16 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+var customHeader = "custom-key";
+var customHeaderValue = "custom-key-value";
+
+self.onmessage = function(event) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", event.data, false);
+ xhr.setRequestHeader(customHeader, customHeaderValue);
+ xhr.send();
+ postMessage({ response: xhr.responseText, header: customHeader });
+}
diff --git a/dom/xhr/tests/xhr2_worker.js b/dom/xhr/tests/xhr2_worker.js
new file mode 100644
index 000000000..d355020c9
--- /dev/null
+++ b/dom/xhr/tests/xhr2_worker.js
@@ -0,0 +1,163 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function(event) {
+ const url = event.data;
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, false);
+ xhr.send();
+
+ const refText = xhr.responseText;
+
+ function getResponse(type) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, false);
+ if (type !== undefined) {
+ xhr.responseType = type;
+ }
+ xhr.send();
+ return xhr.response;
+ }
+
+ if (getResponse() != refText) {
+ throw new Error("unset responseType failed");
+ }
+
+ if (getResponse("") != refText) {
+ throw new Error("'' responseType failed");
+ }
+
+ if (getResponse("text") != refText) {
+ throw new Error("'text' responseType failed");
+ }
+
+ var array = new Uint8Array(getResponse("arraybuffer"));
+ if (String.fromCharCode.apply(String, array) != refText) {
+ throw new Error("'arraybuffer' responseType failed");
+ }
+
+ var blob = getResponse("blob");
+ if (new FileReaderSync().readAsText(blob) != refText) {
+ throw new Error("'blob' responseType failed");
+ }
+
+ // Make sure that we get invalid state exceptions when getting the wrong
+ // property.
+
+ function testResponseTextException(type) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, false);
+ xhr.responseType = type;
+ xhr.send();
+
+ var exception;
+
+ try {
+ xhr.responseText;
+ }
+ catch(e) {
+ exception = e;
+ }
+
+ if (!exception) {
+ throw new Error("Failed to throw when getting responseText on '" + type +
+ "' type");
+ }
+
+ if (exception.name != "InvalidStateError") {
+ throw new Error("Unexpected error when getting responseText on '" + type +
+ "' type");
+ }
+
+ if (exception.code != DOMException.INVALID_STATE_ERR) {
+ throw new Error("Unexpected error code when getting responseText on '" + type +
+ "' type");
+ }
+ }
+
+ testResponseTextException("arraybuffer");
+ testResponseTextException("blob");
+
+ // Make sure "document" works, but returns text.
+ xhr = new XMLHttpRequest();
+
+ if (xhr.responseType != "text") {
+ throw new Error("Default value for responseType is wrong!");
+ }
+
+ xhr.open("GET", url, false);
+ xhr.responseType = "document";
+ xhr.send();
+
+ if (xhr.responseText != refText) {
+ throw new Error("'document' type not working correctly");
+ }
+
+ // Make sure setting responseType before open or after send fails.
+ var exception;
+
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", url);
+ xhr.responseType = "text";
+ xhr.onload = function(event) {
+ if (event.target.response != refText) {
+ throw new Error("Bad response!");
+ }
+
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", url);
+ xhr.responseType = "moz-chunked-text";
+
+ var lastIndex = 0;
+ xhr.onprogress = function(event) {
+ if (refText.substr(lastIndex, xhr.response.length) != xhr.response) {
+ throw new Error("Bad chunk!");
+ }
+
+ lastIndex += xhr.response.length;
+ };
+
+ xhr.onload = function(event) {
+ if (lastIndex != refText.length) {
+ throw new Error("Didn't see all the data!");
+ }
+
+ setTimeout(function() {
+ if (xhr.response !== null) {
+ throw new Error("Should have gotten null response outside of event!");
+ }
+ postMessage("done");
+ }, 0);
+ }
+
+ xhr.send(null);
+ };
+ xhr.send();
+
+ exception = null;
+
+ try {
+ xhr.responseType = "arraybuffer";
+ }
+ catch(e) {
+ exception = e;
+ }
+
+ if (!exception) {
+ throw new Error("Failed to throw when setting responseType after " +
+ "calling send()");
+ }
+
+ if (exception.name != "InvalidStateError") {
+ throw new Error("Unexpected error when setting responseType after " +
+ "calling send()");
+ }
+
+ if (exception.code != DOMException.INVALID_STATE_ERR) {
+ throw new Error("Unexpected error code when setting responseType after " +
+ "calling send()");
+ }
+}
diff --git a/dom/xhr/tests/xhrAbort_worker.js b/dom/xhr/tests/xhrAbort_worker.js
new file mode 100644
index 000000000..281781761
--- /dev/null
+++ b/dom/xhr/tests/xhrAbort_worker.js
@@ -0,0 +1,92 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+function runTest() {
+ var xhr = new XMLHttpRequest();
+
+ var events = [];
+ function pushEvent(event) {
+ var readyState, responseText, status, statusText;
+
+ try {
+ readyState = xhr.readyState;
+ }
+ catch (e) {
+ readyState = "[exception]";
+ }
+
+ try {
+ responseText = xhr.responseText;
+ }
+ catch (e) {
+ responseText = "[exception]";
+ }
+
+ try {
+ status = xhr.status;
+ }
+ catch (e) {
+ status = "[exception]";
+ }
+
+ try {
+ statusText = xhr.statusText;
+ }
+ catch (e) {
+ statusText = "[exception]";
+ }
+
+ var str = event.type + "(" + readyState + ", '" + responseText + "', " +
+ status + ", '" + statusText + "'";
+ if ((("ProgressEvent" in this) && event instanceof ProgressEvent) ||
+ (("WorkerProgressEvent" in this) && event instanceof WorkerProgressEvent)) {
+ str += ", progressEvent";
+ }
+ str += ")";
+
+ events.push(str);
+ }
+
+ xhr.onerror = function(event) {
+ throw new Error("Error: " + xhr.statusText);
+ }
+
+ xhr.onload = function(event) {
+ throw new Error("Shouldn't have gotten load event!");
+ };
+
+ var seenAbort;
+ xhr.onabort = function(event) {
+ if (seenAbort) {
+ throw new Error("Already seen the abort event!");
+ }
+ seenAbort = true;
+
+ pushEvent(event);
+ postMessage(events);
+ };
+
+ xhr.onreadystatechange = function(event) {
+ pushEvent(event);
+ if (xhr.readyState == xhr.HEADERS_RECEIVED) {
+ xhr.abort();
+ }
+ };
+
+ xhr.open("GET", "worker_testXHR.txt");
+ xhr.overrideMimeType("text/plain");
+ xhr.send(null);
+}
+
+function messageListener(event) {
+ switch (event.data) {
+ case "start":
+ runTest();
+ break;
+ default:
+ throw new Error("Bad message!");
+ }
+}
+
+addEventListener("message", messageListener, false);
diff --git a/dom/xhr/tests/xhr_implicit_cancel_worker.js b/dom/xhr/tests/xhr_implicit_cancel_worker.js
new file mode 100644
index 000000000..2045452ca
--- /dev/null
+++ b/dom/xhr/tests/xhr_implicit_cancel_worker.js
@@ -0,0 +1,10 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "worker_testXHR.txt");
+xhr.send(null);
+xhr.open("GET", "worker_testXHR.txt");
+xhr.send(null);
+postMessage("done");
diff --git a/dom/xhr/tests/xhr_worker.js b/dom/xhr/tests/xhr_worker.js
new file mode 100644
index 000000000..178c391b6
--- /dev/null
+++ b/dom/xhr/tests/xhr_worker.js
@@ -0,0 +1,84 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+var xhr = new XMLHttpRequest();
+
+function onload(event) {
+ if (event.target != xhr) {
+ throw "onload event.target != xhr";
+ }
+
+ if (event.target.status != 200) {
+ var message = { type: "error",
+ error: event.target.status };
+ postMessage(message);
+ }
+
+ var message = { type: "load",
+ data: xhr.responseText };
+ postMessage(message);
+}
+
+xhr.onload = onload;
+xhr.addEventListener("load", onload, false);
+xhr.removeEventListener("load", onload, false);
+if (!xhr.onload) {
+ var message = { type: "error",
+ error: "Lost message listener!" };
+ postMessage(message);
+}
+
+xhr.onerror = function(event) {
+ if (event.target != xhr) {
+ throw "onerror event.target != xhr";
+ }
+ var message = { type: "error",
+ error: event.target.status };
+ postMessage(message);
+};
+xhr.onerror = xhr.onerror;
+if (!xhr.onerror || xhr.onerror != xhr.onerror) {
+ throw "onerror wasn't set properly";
+}
+
+function onprogress(event) {
+ if (event.target != xhr) {
+ throw "onprogress event.target != xhr";
+ }
+ var message = { type: "progress",
+ current: event.loaded,
+ total: event.total };
+ postMessage(message);
+}
+xhr.addEventListener("progress", onprogress, false);
+
+xhr.addEventListener("foopety", function(event) {}, false);
+xhr.removeEventListener("doopety", function(event) {}, false);
+
+xhr.onloadend = function(event) {
+ var message = { type: "loadend" };
+ postMessage(message);
+}
+
+var upload = xhr.upload;
+upload.onprogress = function(event) { };
+upload.addEventListener("foo", function(event) { }, false);
+upload.removeEventListener("foo", function(event) { }, false);
+upload.addEventListener("load", function(event) { }, false);
+upload.removeEventListener("foo", function(event) { }, false);
+upload.onload = function(event) {
+ var message = { type: "upload.load" };
+ postMessage(message);
+}
+
+onmessage = function(event) {
+ if (xhr.DONE != 4 || XMLHttpRequest.DONE != 4) {
+ throw "xhr constants not correct!";
+ }
+ if (xhr.readystate > xhr.UNSENT) {
+ throw "XHR already running!";
+ }
+ xhr.open("POST", event.data);
+ xhr.send("Data to send");
+}