summaryrefslogtreecommitdiffstats
path: root/dom/fetch
diff options
context:
space:
mode:
Diffstat (limited to 'dom/fetch')
-rw-r--r--dom/fetch/BodyExtractor.cpp182
-rw-r--r--dom/fetch/BodyExtractor.h44
-rw-r--r--dom/fetch/ChannelInfo.cpp113
-rw-r--r--dom/fetch/ChannelInfo.h77
-rw-r--r--dom/fetch/ChannelInfo.ipdlh14
-rw-r--r--dom/fetch/EmptyBody.cpp84
-rw-r--r--dom/fetch/EmptyBody.h70
-rw-r--r--dom/fetch/Fetch.cpp1537
-rw-r--r--dom/fetch/Fetch.h262
-rw-r--r--dom/fetch/FetchDriver.cpp1601
-rw-r--r--dom/fetch/FetchDriver.h210
-rw-r--r--dom/fetch/FetchIPCTypes.h56
-rw-r--r--dom/fetch/FetchObserver.cpp81
-rw-r--r--dom/fetch/FetchObserver.h47
-rw-r--r--dom/fetch/FetchStreamReader.cpp384
-rw-r--r--dom/fetch/FetchStreamReader.h76
-rw-r--r--dom/fetch/FetchTypes.ipdlh86
-rw-r--r--dom/fetch/FetchUtil.cpp569
-rw-r--r--dom/fetch/FetchUtil.h77
-rw-r--r--dom/fetch/Headers.cpp87
-rw-r--r--dom/fetch/Headers.h128
-rw-r--r--dom/fetch/InternalHeaders.cpp637
-rw-r--r--dom/fetch/InternalHeaders.h178
-rw-r--r--dom/fetch/InternalRequest.cpp399
-rw-r--r--dom/fetch/InternalRequest.h428
-rw-r--r--dom/fetch/InternalResponse.cpp361
-rw-r--r--dom/fetch/InternalResponse.h383
-rw-r--r--dom/fetch/Request.cpp662
-rw-r--r--dom/fetch/Request.h143
-rw-r--r--dom/fetch/Response.cpp498
-rw-r--r--dom/fetch/Response.h136
-rw-r--r--dom/fetch/moz.build65
-rw-r--r--dom/fetch/tests/.eslintrc.js5
-rw-r--r--dom/fetch/tests/browser.ini2
-rw-r--r--dom/fetch/tests/browser_blobFromFile.js62
-rw-r--r--dom/fetch/tests/crashtests/1577196.html26
-rw-r--r--dom/fetch/tests/crashtests/1664514.html6
-rw-r--r--dom/fetch/tests/crashtests/crashtests.list2
-rw-r--r--dom/fetch/tests/crashtests/url.url5
-rw-r--r--dom/fetch/tests/mochitest.ini2
-rw-r--r--dom/fetch/tests/test_ext_response_constructor.html46
-rw-r--r--dom/fetch/tests/test_invalid_header_exception.html39
42 files changed, 9870 insertions, 0 deletions
diff --git a/dom/fetch/BodyExtractor.cpp b/dom/fetch/BodyExtractor.cpp
new file mode 100644
index 0000000000..c4a6e3a399
--- /dev/null
+++ b/dom/fetch/BodyExtractor.cpp
@@ -0,0 +1,182 @@
+/* -*- 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 "BodyExtractor.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/dom/XMLHttpRequest.h"
+#include "mozilla/UniquePtr.h"
+#include "nsContentUtils.h"
+#include "nsDOMSerializer.h"
+#include "nsIGlobalObject.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIStorageStream.h"
+#include "nsStringStream.h"
+
+namespace mozilla::dom {
+
+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), Span(data, aDataLength), NS_ASSIGNMENT_COPY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ stream.forget(aResult);
+
+ return NS_OK;
+}
+
+template <>
+nsresult BodyExtractor<const ArrayBuffer>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
+ mBody->ComputeState();
+ return GetBufferDataAsStream(mBody->Data(), mBody->Length(), aResult,
+ aContentLength, aContentTypeWithCharset,
+ aCharset);
+}
+
+template <>
+nsresult BodyExtractor<const ArrayBufferView>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
+ mBody->ComputeState();
+ return GetBufferDataAsStream(mBody->Data(), mBody->Length(), aResult,
+ aContentLength, aContentTypeWithCharset,
+ aCharset);
+}
+
+template <>
+nsresult BodyExtractor<Document>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
+ NS_ENSURE_STATE(mBody);
+ 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()) {
+ aContentTypeWithCharset.AssignLiteral("text/html;charset=UTF-8");
+
+ 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 {
+ aContentTypeWithCharset.AssignLiteral("application/xml;charset=UTF-8");
+
+ auto serializer = MakeUnique<nsDOMSerializer>();
+
+ // Make sure to use the encoding we'll send
+ ErrorResult res;
+ serializer->SerializeToStream(*mBody, output, u"UTF-8"_ns, res);
+ if (NS_WARN_IF(res.Failed())) {
+ return res.StealNSResult();
+ }
+ }
+
+ 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 BodyExtractor<const nsAString>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
+ nsCString encoded;
+ if (!CopyUTF16toUTF8(*mBody, encoded, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t encodedLength = encoded.Length();
+ nsresult rv = NS_NewCStringInputStream(aResult, std::move(encoded));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aContentLength = encodedLength;
+ aContentTypeWithCharset.AssignLiteral("text/plain;charset=UTF-8");
+ aCharset.AssignLiteral("UTF-8");
+ return NS_OK;
+}
+
+template <>
+nsresult BodyExtractor<nsIInputStream>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
+ aContentTypeWithCharset.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 BodyExtractor<const Blob>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
+ return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset,
+ aCharset);
+}
+
+template <>
+nsresult BodyExtractor<const FormData>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
+ return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset,
+ aCharset);
+}
+
+template <>
+nsresult BodyExtractor<const URLSearchParams>::GetAsStream(
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
+ return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset,
+ aCharset);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/BodyExtractor.h b/dom/fetch/BodyExtractor.h
new file mode 100644
index 0000000000..e612306d1d
--- /dev/null
+++ b/dom/fetch/BodyExtractor.h
@@ -0,0 +1,44 @@
+/* -*- 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_BodyExtractor_h
+#define mozilla_dom_BodyExtractor_h
+
+#include "nsString.h"
+
+class nsIInputStream;
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class BodyExtractorBase {
+ public:
+ virtual nsresult GetAsStream(nsIInputStream** aResult,
+ uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const = 0;
+};
+
+// The implementation versions of this template are:
+// ArrayBuffer, ArrayBufferView, Blob, FormData,
+// URLSearchParams, nsAString, Document, nsIInputStream.
+template <typename Type>
+class BodyExtractor final : public BodyExtractorBase {
+ Type* mBody;
+
+ public:
+ explicit BodyExtractor(Type* aBody) : mBody(aBody) {}
+
+ nsresult GetAsStream(nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BodyExtractor_h
diff --git a/dom/fetch/ChannelInfo.cpp b/dom/fetch/ChannelInfo.cpp
new file mode 100644
index 0000000000..62e6287b0c
--- /dev/null
+++ b/dom/fetch/ChannelInfo.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 "mozilla/dom/ChannelInfo.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "mozilla/dom/Document.h"
+#include "nsIGlobalObject.h"
+#include "nsIHttpChannel.h"
+#include "nsSerializationHelper.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/ipc/ChannelInfo.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+void ChannelInfo::InitFromDocument(Document* aDoc) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mInited, "Cannot initialize the object twice");
+
+ nsCOMPtr<nsISupports> securityInfo = aDoc->GetSecurityInfo();
+ if (securityInfo) {
+ SetSecurityInfo(securityInfo);
+ }
+
+ mInited = true;
+}
+
+void ChannelInfo::InitFromChannel(nsIChannel* aChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mInited, "Cannot initialize the object twice");
+
+ nsCOMPtr<nsISupports> securityInfo;
+ aChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (securityInfo) {
+ SetSecurityInfo(securityInfo);
+ }
+
+ mInited = true;
+}
+
+void ChannelInfo::InitFromChromeGlobal(nsIGlobalObject* aGlobal) {
+ MOZ_ASSERT(!mInited, "Cannot initialize the object twice");
+ MOZ_ASSERT(aGlobal);
+
+ MOZ_RELEASE_ASSERT(aGlobal->PrincipalOrNull()->IsSystemPrincipal());
+
+ mSecurityInfo.Truncate();
+ mInited = true;
+}
+
+void ChannelInfo::InitFromIPCChannelInfo(
+ const mozilla::ipc::IPCChannelInfo& aChannelInfo) {
+ MOZ_ASSERT(!mInited, "Cannot initialize the object twice");
+
+ mSecurityInfo = aChannelInfo.securityInfo();
+
+ mInited = true;
+}
+
+void ChannelInfo::SetSecurityInfo(nsISupports* aSecurityInfo) {
+ MOZ_ASSERT(mSecurityInfo.IsEmpty(), "security info should only be set once");
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(aSecurityInfo);
+ if (!serializable) {
+ NS_WARNING(
+ "A non-serializable object was passed to "
+ "InternalResponse::SetSecurityInfo");
+ return;
+ }
+ NS_SerializeToString(serializable, mSecurityInfo);
+}
+
+nsresult ChannelInfo::ResurrectInfoOnChannel(nsIChannel* aChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mInited);
+
+ if (!mSecurityInfo.IsEmpty()) {
+ nsCOMPtr<nsISupports> infoObj;
+ nsresult rv = NS_DeserializeObject(mSecurityInfo, getter_AddRefs(infoObj));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ MOZ_ASSERT(httpChannel);
+ net::HttpBaseChannel* httpBaseChannel =
+ static_cast<net::HttpBaseChannel*>(httpChannel.get());
+ rv = httpBaseChannel->OverrideSecurityInfo(infoObj);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+mozilla::ipc::IPCChannelInfo ChannelInfo::AsIPCChannelInfo() const {
+ // This may be called when mInited is false, for example if we try to store
+ // a synthesized Response object into the Cache. Uninitialized and empty
+ // ChannelInfo objects are indistinguishable at the IPC level, so this is
+ // fine.
+
+ IPCChannelInfo ipcInfo;
+
+ ipcInfo.securityInfo() = mSecurityInfo;
+
+ return ipcInfo;
+}
diff --git a/dom/fetch/ChannelInfo.h b/dom/fetch/ChannelInfo.h
new file mode 100644
index 0000000000..73dcfbf297
--- /dev/null
+++ b/dom/fetch/ChannelInfo.h
@@ -0,0 +1,77 @@
+/* -*- 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_ChannelInfo_h
+#define mozilla_dom_ChannelInfo_h
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class nsIChannel;
+class nsIGlobalObject;
+class nsIURI;
+
+namespace mozilla {
+namespace ipc {
+class IPCChannelInfo;
+} // namespace ipc
+
+namespace dom {
+
+class Document;
+
+// This class represents the information related to a Response that we
+// retrieve from the corresponding channel that is used to perform the fetch.
+//
+// When adding new members to this object, the following code needs to be
+// updated:
+// * IPCChannelInfo
+// * InitFromChannel and InitFromIPCChannelInfo members
+// * ResurrectInfoOnChannel member
+// * AsIPCChannelInfo member
+// * constructors and assignment operators for this class.
+// * DOM Cache schema code (in dom/cache/DBSchema.cpp) to ensure that the newly
+// added member is saved into the DB and loaded from it properly.
+//
+// Care must be taken when initializing this object, or when calling
+// ResurrectInfoOnChannel(). This object cannot be initialized twice, and
+// ResurrectInfoOnChannel() cannot be called on it before it has been
+// initialized. There are assertions ensuring these invariants.
+class ChannelInfo final {
+ public:
+ typedef mozilla::ipc::IPCChannelInfo IPCChannelInfo;
+
+ ChannelInfo() : mInited(false) {}
+
+ ChannelInfo(const ChannelInfo& aRHS) = default;
+
+ ChannelInfo& operator=(const ChannelInfo& aRHS) = default;
+
+ void InitFromDocument(Document* aDoc);
+ void InitFromChannel(nsIChannel* aChannel);
+ void InitFromChromeGlobal(nsIGlobalObject* aGlobal);
+ void InitFromIPCChannelInfo(const IPCChannelInfo& aChannelInfo);
+
+ // This restores every possible information stored from a previous channel
+ // object on a new one.
+ nsresult ResurrectInfoOnChannel(nsIChannel* aChannel);
+
+ bool IsInitialized() const { return mInited; }
+
+ IPCChannelInfo AsIPCChannelInfo() const;
+
+ private:
+ void SetSecurityInfo(nsISupports* aSecurityInfo);
+
+ private:
+ nsCString mSecurityInfo;
+ bool mInited;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ChannelInfo_h
diff --git a/dom/fetch/ChannelInfo.ipdlh b/dom/fetch/ChannelInfo.ipdlh
new file mode 100644
index 0000000000..605009e120
--- /dev/null
+++ b/dom/fetch/ChannelInfo.ipdlh
@@ -0,0 +1,14 @@
+/* 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/. */
+
+namespace mozilla {
+namespace ipc {
+
+struct IPCChannelInfo
+{
+ nsCString securityInfo;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/dom/fetch/EmptyBody.cpp b/dom/fetch/EmptyBody.cpp
new file mode 100644
index 0000000000..18b521ac02
--- /dev/null
+++ b/dom/fetch/EmptyBody.cpp
@@ -0,0 +1,84 @@
+/* -*- 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 "EmptyBody.h"
+
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsStringStream.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ADDREF_INHERITED(EmptyBody, FetchBody<EmptyBody>)
+NS_IMPL_RELEASE_INHERITED(EmptyBody, FetchBody<EmptyBody>)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(EmptyBody)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EmptyBody, FetchBody<EmptyBody>)
+ AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAbortSignalImpl)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EmptyBody,
+ FetchBody<EmptyBody>)
+ AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbortSignalImpl)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(EmptyBody, FetchBody<EmptyBody>)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EmptyBody)
+NS_INTERFACE_MAP_END_INHERITING(FetchBody<EmptyBody>)
+
+EmptyBody::EmptyBody(nsIGlobalObject* aGlobal,
+ mozilla::ipc::PrincipalInfo* aPrincipalInfo,
+ AbortSignalImpl* aAbortSignalImpl,
+ const nsACString& aMimeType,
+ already_AddRefed<nsIInputStream> aBodyStream)
+ : FetchBody<EmptyBody>(aGlobal),
+ mAbortSignalImpl(aAbortSignalImpl),
+ mMimeType(aMimeType),
+ mBodyStream(std::move(aBodyStream)) {
+ if (aPrincipalInfo) {
+ mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*aPrincipalInfo);
+ }
+}
+
+EmptyBody::~EmptyBody() = default;
+
+/* static */
+already_AddRefed<EmptyBody> EmptyBody::Create(
+ nsIGlobalObject* aGlobal, mozilla::ipc::PrincipalInfo* aPrincipalInfo,
+ AbortSignalImpl* aAbortSignalImpl, const nsACString& aMimeType,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIInputStream> bodyStream;
+ aRv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), ""_ns);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<EmptyBody> emptyBody =
+ new EmptyBody(aGlobal, aPrincipalInfo, aAbortSignalImpl, aMimeType,
+ bodyStream.forget());
+ return emptyBody.forget();
+}
+
+void EmptyBody::GetBody(nsIInputStream** aStream, int64_t* aBodyLength) {
+ MOZ_ASSERT(aStream);
+
+ if (aBodyLength) {
+ *aBodyLength = 0;
+ }
+
+ nsCOMPtr<nsIInputStream> bodyStream = mBodyStream;
+ bodyStream.forget(aStream);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/EmptyBody.h b/dom/fetch/EmptyBody.h
new file mode 100644
index 0000000000..dce71d4ae2
--- /dev/null
+++ b/dom/fetch/EmptyBody.h
@@ -0,0 +1,70 @@
+/* -*- 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_EmptyBody_h
+#define mozilla_dom_EmptyBody_h
+
+#include "nsISupportsImpl.h"
+
+#include "mozilla/dom/Fetch.h"
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class EmptyBody final : public FetchBody<EmptyBody> {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(EmptyBody,
+ FetchBody<EmptyBody>)
+
+ public:
+ static already_AddRefed<EmptyBody> Create(
+ nsIGlobalObject* aGlobal, mozilla::ipc::PrincipalInfo* aPrincipalInfo,
+ AbortSignalImpl* aAbortSignalImpl, const nsACString& aMimeType,
+ ErrorResult& aRv);
+
+ nsIGlobalObject* GetParentObject() const { return mOwner; }
+
+ AbortSignalImpl* GetSignalImpl() const override { return mAbortSignalImpl; }
+
+ const UniquePtr<mozilla::ipc::PrincipalInfo>& GetPrincipalInfo() const {
+ return mPrincipalInfo;
+ }
+
+ void GetMimeType(nsACString& aMimeType) { aMimeType = mMimeType; }
+
+ void GetBody(nsIInputStream** aStream, int64_t* aBodyLength = nullptr);
+
+ using FetchBody::BodyBlobURISpec;
+
+ const nsACString& BodyBlobURISpec() const { return EmptyCString(); }
+
+ using FetchBody::BodyLocalPath;
+
+ const nsAString& BodyLocalPath() const { return EmptyString(); }
+
+ private:
+ EmptyBody(nsIGlobalObject* aGlobal,
+ mozilla::ipc::PrincipalInfo* aPrincipalInfo,
+ AbortSignalImpl* aAbortSignalImpl, const nsACString& aMimeType,
+ already_AddRefed<nsIInputStream> mBodyStream);
+
+ ~EmptyBody();
+
+ UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
+ RefPtr<AbortSignalImpl> mAbortSignalImpl;
+ nsCString mMimeType;
+ nsCOMPtr<nsIInputStream> mBodyStream;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_EmptyBody_h
diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp
new file mode 100644
index 0000000000..d47c09461a
--- /dev/null
+++ b/dom/fetch/Fetch.cpp
@@ -0,0 +1,1537 @@
+/* -*- 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 "Fetch.h"
+
+#include "mozilla/dom/Document.h"
+#include "nsIGlobalObject.h"
+
+#include "nsDOMString.h"
+#include "nsJSUtils.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsProxyRelease.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BodyConsumer.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/FetchDriver.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/RemoteWorkerChild.h"
+#include "mozilla/dom/Request.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/net/CookieJarSettings.h"
+
+#include "BodyExtractor.h"
+#include "EmptyBody.h"
+#include "FetchObserver.h"
+#include "InternalRequest.h"
+#include "InternalResponse.h"
+
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerScope.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+void AbortStream(JSContext* aCx, JS::Handle<JSObject*> aStream,
+ ErrorResult& aRv) {
+ aRv.MightThrowJSException();
+
+ bool isReadable;
+ if (!JS::ReadableStreamIsReadable(aCx, aStream, &isReadable)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ if (!isReadable) {
+ return;
+ }
+
+ RefPtr<DOMException> e = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, e, &value)) {
+ return;
+ }
+
+ if (!JS::ReadableStreamError(aCx, aStream, value)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ }
+}
+
+} // namespace
+
+class AbortSignalMainThread final : public AbortSignalImpl {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(AbortSignalMainThread)
+
+ explicit AbortSignalMainThread(bool aAborted) : AbortSignalImpl(aAborted) {}
+
+ private:
+ ~AbortSignalMainThread() = default;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AbortSignalMainThread)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbortSignalMainThread)
+ AbortSignalImpl::Unlink(static_cast<AbortSignalImpl*>(tmp));
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbortSignalMainThread)
+ AbortSignalImpl::Traverse(static_cast<AbortSignalImpl*>(tmp), cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbortSignalMainThread)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AbortSignalMainThread)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AbortSignalMainThread)
+
+class AbortSignalProxy;
+
+class WorkerSignalFollower final : public AbortFollower {
+ public:
+ // This runnable propagates changes from the AbortSignalImpl on workers to the
+ // AbortSignalImpl on main-thread.
+ class AbortSignalProxyRunnable final : public Runnable {
+ RefPtr<AbortSignalProxy> mProxy;
+
+ public:
+ explicit AbortSignalProxyRunnable(AbortSignalProxy* aProxy)
+ : Runnable("dom::WorkerSignalFollower::AbortSignalProxyRunnable"),
+ mProxy(aProxy) {}
+
+ NS_IMETHOD Run() override;
+ };
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(WorkerSignalFollower)
+
+ void RunAbortAlgorithm() override {}
+
+ private:
+ ~WorkerSignalFollower() = default;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerSignalFollower)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkerSignalFollower)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkerSignalFollower)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WorkerSignalFollower)
+ AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WorkerSignalFollower)
+ AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerSignalFollower)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+// This class orchestrates the proxying of AbortSignal operations between the
+// main thread and a worker thread.
+class AbortSignalProxy final : public AbortFollower {
+ // This is created and released on the main-thread.
+ RefPtr<AbortSignalImpl> mSignalImplMainThread;
+
+ // The main-thread event target for runnable dispatching.
+ nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+
+ // This value is used only when creating mSignalImplMainThread on the main
+ // thread, to create it in already-aborted state if necessary. It does *not*
+ // reflect the instantaneous is-aborted status of the worker thread's
+ // AbortSignal.
+ const bool mAborted;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ AbortSignalProxy(AbortSignalImpl* aSignalImpl,
+ nsIEventTarget* aMainThreadEventTarget)
+ : mMainThreadEventTarget(aMainThreadEventTarget),
+ mAborted(aSignalImpl->Aborted()) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mMainThreadEventTarget);
+ Follow(aSignalImpl);
+ }
+
+ // AbortFollower
+ void RunAbortAlgorithm() override;
+
+ AbortSignalImpl* GetOrCreateSignalImplForMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mSignalImplMainThread) {
+ mSignalImplMainThread = new AbortSignalMainThread(mAborted);
+ }
+ return mSignalImplMainThread;
+ }
+
+ AbortSignalImpl* GetSignalImplForTargetThread() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ return Signal();
+ }
+
+ nsIEventTarget* MainThreadEventTarget() { return mMainThreadEventTarget; }
+
+ void Shutdown() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ Unfollow();
+ }
+
+ private:
+ ~AbortSignalProxy() {
+ NS_ProxyRelease("AbortSignalProxy::mSignalImplMainThread",
+ mMainThreadEventTarget, mSignalImplMainThread.forget());
+ }
+};
+
+NS_IMPL_ISUPPORTS0(AbortSignalProxy)
+
+NS_IMETHODIMP WorkerSignalFollower::AbortSignalProxyRunnable::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+ AbortSignalImpl* signalImpl = mProxy->GetOrCreateSignalImplForMainThread();
+ signalImpl->SignalAbort();
+ return NS_OK;
+}
+
+void AbortSignalProxy::RunAbortAlgorithm() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ using AbortSignalProxyRunnable =
+ WorkerSignalFollower::AbortSignalProxyRunnable;
+ RefPtr<AbortSignalProxyRunnable> runnable =
+ new AbortSignalProxyRunnable(this);
+ MainThreadEventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+}
+
+class WorkerFetchResolver final : public FetchDriverObserver {
+ // Thread-safe:
+ RefPtr<PromiseWorkerProxy> mPromiseProxy;
+ RefPtr<AbortSignalProxy> mSignalProxy;
+
+ // Touched only on the worker thread.
+ RefPtr<FetchObserver> mFetchObserver;
+ RefPtr<WeakWorkerRef> mWorkerRef;
+ bool mIsShutdown;
+
+ public:
+ // Returns null if worker is shutting down.
+ static already_AddRefed<WorkerFetchResolver> Create(
+ WorkerPrivate* aWorkerPrivate, Promise* aPromise,
+ AbortSignalImpl* aSignalImpl, FetchObserver* aObserver) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ RefPtr<PromiseWorkerProxy> proxy =
+ PromiseWorkerProxy::Create(aWorkerPrivate, aPromise);
+ if (!proxy) {
+ return nullptr;
+ }
+
+ RefPtr<AbortSignalProxy> signalProxy;
+ if (aSignalImpl) {
+ signalProxy = new AbortSignalProxy(
+ aSignalImpl, aWorkerPrivate->MainThreadEventTarget());
+ }
+
+ RefPtr<WorkerFetchResolver> r =
+ new WorkerFetchResolver(proxy, signalProxy, aObserver);
+
+ RefPtr<WeakWorkerRef> workerRef = WeakWorkerRef::Create(
+ aWorkerPrivate, [r]() { r->Shutdown(r->mWorkerRef->GetPrivate()); });
+ if (NS_WARN_IF(!workerRef)) {
+ return nullptr;
+ }
+
+ r->mWorkerRef = std::move(workerRef);
+
+ return r.forget();
+ }
+
+ AbortSignalImpl* GetAbortSignalForMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mSignalProxy) {
+ return nullptr;
+ }
+
+ return mSignalProxy->GetOrCreateSignalImplForMainThread();
+ }
+
+ AbortSignalImpl* GetAbortSignalForTargetThread() {
+ mPromiseProxy->GetWorkerPrivate()->AssertIsOnWorkerThread();
+
+ if (!mSignalProxy) {
+ return nullptr;
+ }
+
+ return mSignalProxy->GetSignalImplForTargetThread();
+ }
+
+ PromiseWorkerProxy* PromiseProxy() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mPromiseProxy;
+ }
+
+ Promise* WorkerPromise(WorkerPrivate* aWorkerPrivate) const {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mIsShutdown);
+
+ return mPromiseProxy->WorkerPromise();
+ }
+
+ FetchObserver* GetFetchObserver(WorkerPrivate* aWorkerPrivate) const {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ return mFetchObserver;
+ }
+
+ void OnResponseAvailableInternal(InternalResponse* aResponse) override;
+
+ void OnResponseEnd(FetchDriverObserver::EndReason eReason) override;
+
+ bool NeedOnDataAvailable() override;
+
+ void OnDataAvailable() override;
+
+ void Shutdown(WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ mIsShutdown = true;
+ mPromiseProxy->CleanUp();
+
+ mFetchObserver = nullptr;
+
+ if (mSignalProxy) {
+ mSignalProxy->Shutdown();
+ }
+
+ mWorkerRef = nullptr;
+ }
+
+ bool IsShutdown(WorkerPrivate* aWorkerPrivate) const {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ return mIsShutdown;
+ }
+
+ private:
+ WorkerFetchResolver(PromiseWorkerProxy* aProxy,
+ AbortSignalProxy* aSignalProxy, FetchObserver* aObserver)
+ : mPromiseProxy(aProxy),
+ mSignalProxy(aSignalProxy),
+ mFetchObserver(aObserver),
+ mIsShutdown(false) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mPromiseProxy);
+ }
+
+ ~WorkerFetchResolver() = default;
+
+ virtual void FlushConsoleReport() override;
+};
+
+class MainThreadFetchResolver final : public FetchDriverObserver {
+ RefPtr<Promise> mPromise;
+ RefPtr<Response> mResponse;
+ RefPtr<FetchObserver> mFetchObserver;
+ RefPtr<AbortSignalImpl> mSignalImpl;
+ const bool mMozErrors;
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+
+ NS_DECL_OWNINGTHREAD
+ public:
+ MainThreadFetchResolver(Promise* aPromise, FetchObserver* aObserver,
+ AbortSignalImpl* aSignalImpl, bool aMozErrors)
+ : mPromise(aPromise),
+ mFetchObserver(aObserver),
+ mSignalImpl(aSignalImpl),
+ mMozErrors(aMozErrors) {}
+
+ void OnResponseAvailableInternal(InternalResponse* aResponse) override;
+
+ void SetLoadGroup(nsILoadGroup* aLoadGroup) { mLoadGroup = aLoadGroup; }
+
+ void OnResponseEnd(FetchDriverObserver::EndReason aReason) override {
+ if (aReason == eAborted) {
+ mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ mFetchObserver = nullptr;
+
+ FlushConsoleReport();
+ }
+
+ bool NeedOnDataAvailable() override;
+
+ void OnDataAvailable() override;
+
+ private:
+ ~MainThreadFetchResolver();
+
+ void FlushConsoleReport() override {
+ mReporter->FlushConsoleReports(mLoadGroup);
+ }
+};
+
+class MainThreadFetchRunnable : public Runnable {
+ RefPtr<WorkerFetchResolver> mResolver;
+ const ClientInfo mClientInfo;
+ const Maybe<ServiceWorkerDescriptor> mController;
+ nsCOMPtr<nsICSPEventListener> mCSPEventListener;
+ SafeRefPtr<InternalRequest> mRequest;
+ UniquePtr<SerializedStackHolder> mOriginStack;
+
+ public:
+ MainThreadFetchRunnable(WorkerFetchResolver* aResolver,
+ const ClientInfo& aClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController,
+ nsICSPEventListener* aCSPEventListener,
+ SafeRefPtr<InternalRequest> aRequest,
+ UniquePtr<SerializedStackHolder>&& aOriginStack)
+ : Runnable("dom::MainThreadFetchRunnable"),
+ mResolver(aResolver),
+ mClientInfo(aClientInfo),
+ mController(aController),
+ mCSPEventListener(aCSPEventListener),
+ mRequest(std::move(aRequest)),
+ mOriginStack(std::move(aOriginStack)) {
+ MOZ_ASSERT(mResolver);
+ }
+
+ NS_IMETHOD
+ Run() override {
+ AssertIsOnMainThread();
+ RefPtr<FetchDriver> fetch;
+ RefPtr<PromiseWorkerProxy> proxy = mResolver->PromiseProxy();
+
+ {
+ // Acquire the proxy mutex while getting data from the WorkerPrivate...
+ MutexAutoLock lock(proxy->Lock());
+ if (proxy->CleanedUp()) {
+ NS_WARNING("Aborting Fetch because worker already shut down");
+ return NS_OK;
+ }
+
+ WorkerPrivate* workerPrivate = proxy->GetWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ nsCOMPtr<nsIPrincipal> principal = workerPrivate->GetPrincipal();
+ MOZ_ASSERT(principal);
+ nsCOMPtr<nsILoadGroup> loadGroup = workerPrivate->GetLoadGroup();
+ MOZ_ASSERT(loadGroup);
+ // We don't track if a worker is spawned from a tracking script for now,
+ // so pass false as the last argument to FetchDriver().
+ fetch = new FetchDriver(mRequest.clonePtr(), principal, loadGroup,
+ workerPrivate->MainThreadEventTarget(),
+ workerPrivate->CookieJarSettings(),
+ workerPrivate->GetPerformanceStorage(), false);
+ nsAutoCString spec;
+ if (proxy->GetWorkerPrivate()->GetBaseURI()) {
+ proxy->GetWorkerPrivate()->GetBaseURI()->GetAsciiSpec(spec);
+ }
+ fetch->SetWorkerScript(spec);
+
+ fetch->SetClientInfo(mClientInfo);
+ fetch->SetController(mController);
+ fetch->SetCSPEventListener(mCSPEventListener);
+ }
+
+ fetch->SetOriginStack(std::move(mOriginStack));
+
+ RefPtr<AbortSignalImpl> signalImpl =
+ mResolver->GetAbortSignalForMainThread();
+
+ // ...but release it before calling Fetch, because mResolver's callback can
+ // be called synchronously and they want the mutex, too.
+ return fetch->Fetch(signalImpl, mResolver);
+ }
+};
+
+already_AddRefed<Promise> FetchRequest(nsIGlobalObject* aGlobal,
+ const RequestOrUSVString& aInput,
+ const RequestInit& aInit,
+ CallerType aCallerType,
+ ErrorResult& aRv) {
+ RefPtr<Promise> p = Promise::Create(aGlobal, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(aGlobal);
+
+ // Double check that we have chrome privileges if the Request's content
+ // policy type has been overridden.
+ MOZ_ASSERT_IF(aInput.IsRequest() &&
+ aInput.GetAsRequest().IsContentPolicyTypeOverridden(),
+ aCallerType == CallerType::System);
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aGlobal)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> jsGlobal(cx, aGlobal->GetGlobalJSObject());
+ GlobalObject global(cx, jsGlobal);
+
+ SafeRefPtr<Request> request =
+ Request::Constructor(global, aInput, aInit, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ SafeRefPtr<InternalRequest> r = request->GetInternalRequest();
+ RefPtr<AbortSignalImpl> signalImpl = request->GetSignalImpl();
+
+ if (signalImpl && signalImpl->Aborted()) {
+ // Already aborted signal rejects immediately.
+ aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+ return nullptr;
+ }
+
+ RefPtr<FetchObserver> observer;
+ if (aInit.mObserve.WasPassed()) {
+ observer = new FetchObserver(aGlobal, signalImpl);
+ aInit.mObserve.Value().HandleEvent(*observer);
+ }
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
+ nsCOMPtr<Document> doc;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsIPrincipal* principal;
+ bool isTrackingFetch = false;
+ if (window) {
+ doc = window->GetExtantDoc();
+ if (!doc) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ principal = doc->NodePrincipal();
+ loadGroup = doc->GetDocumentLoadGroup();
+ cookieJarSettings = doc->CookieJarSettings();
+
+ isTrackingFetch = doc->IsScriptTracking(cx);
+ } else {
+ principal = aGlobal->PrincipalOrNull();
+ if (NS_WARN_IF(!principal)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ cookieJarSettings = mozilla::net::CookieJarSettings::Create();
+ }
+
+ if (!loadGroup) {
+ nsresult rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), principal);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ }
+
+ RefPtr<MainThreadFetchResolver> resolver = new MainThreadFetchResolver(
+ p, observer, signalImpl, request->MozErrors());
+ RefPtr<FetchDriver> fetch =
+ new FetchDriver(std::move(r), principal, loadGroup,
+ aGlobal->EventTargetFor(TaskCategory::Other),
+ cookieJarSettings, nullptr, // PerformanceStorage
+ isTrackingFetch);
+ fetch->SetDocument(doc);
+ resolver->SetLoadGroup(loadGroup);
+ aRv = fetch->Fetch(signalImpl, resolver);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ } else {
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+
+ if (worker->IsServiceWorker()) {
+ r->SetSkipServiceWorker();
+ }
+
+ RefPtr<WorkerFetchResolver> resolver =
+ WorkerFetchResolver::Create(worker, p, signalImpl, observer);
+ if (!resolver) {
+ NS_WARNING("Could not keep the worker alive.");
+ aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+ return nullptr;
+ }
+
+ Maybe<ClientInfo> clientInfo(worker->GlobalScope()->GetClientInfo());
+ if (clientInfo.isNothing()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ UniquePtr<SerializedStackHolder> stack;
+ if (worker->IsWatchedByDevTools()) {
+ stack = GetCurrentStackForNetMonitor(cx);
+ }
+
+ RefPtr<MainThreadFetchRunnable> run = new MainThreadFetchRunnable(
+ resolver, clientInfo.ref(), worker->GlobalScope()->GetController(),
+ worker->CSPEventListener(), std::move(r), std::move(stack));
+ worker->DispatchToMainThread(run.forget());
+ }
+
+ return p.forget();
+}
+
+class ResolveFetchPromise : public Runnable {
+ public:
+ ResolveFetchPromise(Promise* aPromise, Response* aResponse)
+ : Runnable("ResolveFetchPromise"),
+ mPromise(aPromise),
+ mResponse(aResponse) {}
+
+ NS_IMETHOD Run() {
+ mPromise->MaybeResolve(mResponse);
+ return NS_OK;
+ }
+ RefPtr<Promise> mPromise;
+ RefPtr<Response> mResponse;
+};
+
+void MainThreadFetchResolver::OnResponseAvailableInternal(
+ InternalResponse* aResponse) {
+ NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver);
+ AssertIsOnMainThread();
+
+ if (aResponse->Type() != ResponseType::Error) {
+ if (mFetchObserver) {
+ mFetchObserver->SetState(FetchState::Complete);
+ }
+
+ nsCOMPtr<nsIGlobalObject> go = mPromise->GetParentObject();
+ mResponse = new Response(go, aResponse, mSignalImpl);
+ nsCOMPtr<nsPIDOMWindowInner> inner = do_QueryInterface(go);
+ BrowsingContext* bc = inner ? inner->GetBrowsingContext() : nullptr;
+ bc = bc ? bc->Top() : nullptr;
+ if (bc && bc->IsLoading()) {
+ bc->AddDeprioritizedLoadRunner(
+ new ResolveFetchPromise(mPromise, mResponse));
+ } else {
+ mPromise->MaybeResolve(mResponse);
+ }
+ } else {
+ if (mFetchObserver) {
+ mFetchObserver->SetState(FetchState::Errored);
+ }
+
+ if (mMozErrors) {
+ mPromise->MaybeReject(aResponse->GetErrorCode());
+ return;
+ }
+
+ mPromise->MaybeRejectWithTypeError<MSG_FETCH_FAILED>();
+ }
+}
+
+bool MainThreadFetchResolver::NeedOnDataAvailable() {
+ NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver);
+ return !!mFetchObserver;
+}
+
+void MainThreadFetchResolver::OnDataAvailable() {
+ NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver);
+ AssertIsOnMainThread();
+
+ if (!mFetchObserver) {
+ return;
+ }
+
+ if (mFetchObserver->State() == FetchState::Requesting) {
+ mFetchObserver->SetState(FetchState::Responding);
+ }
+}
+
+MainThreadFetchResolver::~MainThreadFetchResolver() {
+ NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver);
+}
+
+class WorkerFetchResponseRunnable final : public MainThreadWorkerRunnable {
+ RefPtr<WorkerFetchResolver> mResolver;
+ // Passed from main thread to worker thread after being initialized.
+ RefPtr<InternalResponse> mInternalResponse;
+
+ public:
+ WorkerFetchResponseRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerFetchResolver* aResolver,
+ InternalResponse* aResponse)
+ : MainThreadWorkerRunnable(aWorkerPrivate),
+ mResolver(aResolver),
+ mInternalResponse(aResponse) {
+ MOZ_ASSERT(mResolver);
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> promise = mResolver->WorkerPromise(aWorkerPrivate);
+ RefPtr<FetchObserver> fetchObserver =
+ mResolver->GetFetchObserver(aWorkerPrivate);
+
+ if (mInternalResponse->Type() != ResponseType::Error) {
+ if (fetchObserver) {
+ fetchObserver->SetState(FetchState::Complete);
+ }
+
+ RefPtr<nsIGlobalObject> global = aWorkerPrivate->GlobalScope();
+ RefPtr<Response> response =
+ new Response(global, mInternalResponse,
+ mResolver->GetAbortSignalForTargetThread());
+ promise->MaybeResolve(response);
+ } else {
+ if (fetchObserver) {
+ fetchObserver->SetState(FetchState::Errored);
+ }
+
+ promise->MaybeRejectWithTypeError<MSG_FETCH_FAILED>();
+ }
+ return true;
+ }
+};
+
+class WorkerDataAvailableRunnable final : public MainThreadWorkerRunnable {
+ RefPtr<WorkerFetchResolver> mResolver;
+
+ public:
+ WorkerDataAvailableRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerFetchResolver* aResolver)
+ : MainThreadWorkerRunnable(aWorkerPrivate), mResolver(aResolver) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<FetchObserver> fetchObserver =
+ mResolver->GetFetchObserver(aWorkerPrivate);
+
+ if (fetchObserver && fetchObserver->State() == FetchState::Requesting) {
+ fetchObserver->SetState(FetchState::Responding);
+ }
+
+ return true;
+ }
+};
+
+class WorkerFetchResponseEndBase {
+ protected:
+ RefPtr<WorkerFetchResolver> mResolver;
+
+ public:
+ explicit WorkerFetchResponseEndBase(WorkerFetchResolver* aResolver)
+ : mResolver(aResolver) {
+ MOZ_ASSERT(aResolver);
+ }
+
+ void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) {
+ mResolver->Shutdown(aWorkerPrivate);
+ }
+};
+
+class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable,
+ public WorkerFetchResponseEndBase {
+ FetchDriverObserver::EndReason mReason;
+
+ public:
+ WorkerFetchResponseEndRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerFetchResolver* aResolver,
+ FetchDriverObserver::EndReason aReason)
+ : MainThreadWorkerRunnable(aWorkerPrivate),
+ WorkerFetchResponseEndBase(aResolver),
+ mReason(aReason) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ if (mResolver->IsShutdown(aWorkerPrivate)) {
+ return true;
+ }
+
+ if (mReason == FetchDriverObserver::eAborted) {
+ mResolver->WorkerPromise(aWorkerPrivate)
+ ->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ WorkerRunInternal(aWorkerPrivate);
+ return true;
+ }
+
+ nsresult Cancel() override {
+ // Execute Run anyway to make sure we cleanup our promise proxy to avoid
+ // leaking the worker thread
+ Run();
+ return WorkerRunnable::Cancel();
+ }
+};
+
+class WorkerFetchResponseEndControlRunnable final
+ : public MainThreadWorkerControlRunnable,
+ public WorkerFetchResponseEndBase {
+ public:
+ WorkerFetchResponseEndControlRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerFetchResolver* aResolver)
+ : MainThreadWorkerControlRunnable(aWorkerPrivate),
+ WorkerFetchResponseEndBase(aResolver) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ WorkerRunInternal(aWorkerPrivate);
+ return true;
+ }
+
+ // Control runnable cancel already calls Run().
+};
+
+void WorkerFetchResolver::OnResponseAvailableInternal(
+ InternalResponse* aResponse) {
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return;
+ }
+
+ RefPtr<WorkerFetchResponseRunnable> r = new WorkerFetchResponseRunnable(
+ mPromiseProxy->GetWorkerPrivate(), this, aResponse);
+
+ if (!r->Dispatch()) {
+ NS_WARNING("Could not dispatch fetch response");
+ }
+}
+
+bool WorkerFetchResolver::NeedOnDataAvailable() {
+ AssertIsOnMainThread();
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ return !!mFetchObserver;
+}
+
+void WorkerFetchResolver::OnDataAvailable() {
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return;
+ }
+
+ RefPtr<WorkerDataAvailableRunnable> r =
+ new WorkerDataAvailableRunnable(mPromiseProxy->GetWorkerPrivate(), this);
+ Unused << r->Dispatch();
+}
+
+void WorkerFetchResolver::OnResponseEnd(
+ FetchDriverObserver::EndReason aReason) {
+ AssertIsOnMainThread();
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return;
+ }
+
+ FlushConsoleReport();
+
+ RefPtr<WorkerFetchResponseEndRunnable> r = new WorkerFetchResponseEndRunnable(
+ mPromiseProxy->GetWorkerPrivate(), this, aReason);
+
+ if (!r->Dispatch()) {
+ RefPtr<WorkerFetchResponseEndControlRunnable> cr =
+ new WorkerFetchResponseEndControlRunnable(
+ mPromiseProxy->GetWorkerPrivate(), this);
+ // This can fail if the worker thread is canceled or killed causing
+ // the PromiseWorkerProxy to give up its WorkerRef immediately,
+ // allowing the worker thread to become Dead.
+ if (!cr->Dispatch()) {
+ NS_WARNING("Failed to dispatch WorkerFetchResponseEndControlRunnable");
+ }
+ }
+}
+
+void WorkerFetchResolver::FlushConsoleReport() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mPromiseProxy);
+
+ if (!mReporter) {
+ return;
+ }
+
+ WorkerPrivate* worker = mPromiseProxy->GetWorkerPrivate();
+ if (!worker) {
+ mReporter->FlushReportsToConsole(0);
+ return;
+ }
+
+ if (worker->IsServiceWorker()) {
+ // Flush to service worker
+ mReporter->FlushReportsToConsoleForServiceWorkerScope(
+ worker->ServiceWorkerScope());
+ return;
+ }
+
+ if (worker->IsSharedWorker()) {
+ // Flush to shared worker
+ worker->GetRemoteWorkerController()->FlushReportsOnMainThread(mReporter);
+ return;
+ }
+
+ // Flush to dedicated worker
+ mReporter->FlushConsoleReports(worker->GetLoadGroup());
+}
+
+nsresult ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit,
+ nsIInputStream** aStream,
+ nsCString& aContentTypeWithCharset,
+ uint64_t& aContentLength) {
+ MOZ_ASSERT(aStream);
+ nsAutoCString charset;
+ aContentTypeWithCharset.SetIsVoid(true);
+
+ if (aBodyInit.IsArrayBuffer()) {
+ BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsArrayBufferView()) {
+ BodyExtractor<const ArrayBufferView> body(
+ &aBodyInit.GetAsArrayBufferView());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsBlob()) {
+ Blob& blob = aBodyInit.GetAsBlob();
+ BodyExtractor<const Blob> body(&blob);
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsFormData()) {
+ FormData& formData = aBodyInit.GetAsFormData();
+ BodyExtractor<const FormData> body(&formData);
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsUSVString()) {
+ BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsURLSearchParams()) {
+ URLSearchParams& usp = aBodyInit.GetAsURLSearchParams();
+ BodyExtractor<const URLSearchParams> body(&usp);
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Should never reach here");
+ return NS_ERROR_FAILURE;
+}
+
+nsresult ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit,
+ nsIInputStream** aStream,
+ nsCString& aContentTypeWithCharset,
+ uint64_t& aContentLength) {
+ MOZ_ASSERT(aStream);
+ MOZ_ASSERT(!*aStream);
+
+ nsAutoCString charset;
+ aContentTypeWithCharset.SetIsVoid(true);
+
+ if (aBodyInit.IsArrayBuffer()) {
+ BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsArrayBufferView()) {
+ BodyExtractor<const ArrayBufferView> body(
+ &aBodyInit.GetAsArrayBufferView());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsBlob()) {
+ BodyExtractor<const Blob> body(&aBodyInit.GetAsBlob());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsFormData()) {
+ BodyExtractor<const FormData> body(&aBodyInit.GetAsFormData());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsUSVString()) {
+ BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsURLSearchParams()) {
+ BodyExtractor<const URLSearchParams> body(
+ &aBodyInit.GetAsURLSearchParams());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Should never reach here");
+ return NS_ERROR_FAILURE;
+}
+
+nsresult ExtractByteStreamFromBody(const fetch::ResponseBodyInit& aBodyInit,
+ nsIInputStream** aStream,
+ nsCString& aContentTypeWithCharset,
+ uint64_t& aContentLength) {
+ MOZ_ASSERT(aStream);
+ MOZ_ASSERT(!*aStream);
+
+ // ReadableStreams should be handled by
+ // BodyExtractorReadableStream::GetAsStream.
+ MOZ_ASSERT(!aBodyInit.IsReadableStream());
+
+ nsAutoCString charset;
+ aContentTypeWithCharset.SetIsVoid(true);
+
+ if (aBodyInit.IsArrayBuffer()) {
+ BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsArrayBufferView()) {
+ BodyExtractor<const ArrayBufferView> body(
+ &aBodyInit.GetAsArrayBufferView());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsBlob()) {
+ BodyExtractor<const Blob> body(&aBodyInit.GetAsBlob());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsFormData()) {
+ BodyExtractor<const FormData> body(&aBodyInit.GetAsFormData());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsUSVString()) {
+ BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ if (aBodyInit.IsURLSearchParams()) {
+ BodyExtractor<const URLSearchParams> body(
+ &aBodyInit.GetAsURLSearchParams());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Should never reach here");
+ return NS_ERROR_FAILURE;
+}
+
+template <class Derived>
+FetchBody<Derived>::FetchBody(nsIGlobalObject* aOwner)
+ : mOwner(aOwner),
+ mWorkerPrivate(nullptr),
+ mReadableStreamBody(nullptr),
+ mReadableStreamReader(nullptr),
+ mBodyUsed(false) {
+ MOZ_ASSERT(aOwner);
+
+ if (!NS_IsMainThread()) {
+ mWorkerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(mWorkerPrivate);
+ mMainThreadEventTarget = mWorkerPrivate->MainThreadEventTarget();
+ } else {
+ mMainThreadEventTarget = aOwner->EventTargetFor(TaskCategory::Other);
+ }
+
+ MOZ_ASSERT(mMainThreadEventTarget);
+}
+
+template FetchBody<Request>::FetchBody(nsIGlobalObject* aOwner);
+
+template FetchBody<Response>::FetchBody(nsIGlobalObject* aOwner);
+
+template <class Derived>
+FetchBody<Derived>::~FetchBody() {
+ Unfollow();
+}
+
+template FetchBody<Request>::~FetchBody();
+
+template FetchBody<Response>::~FetchBody();
+
+template <class Derived>
+bool FetchBody<Derived>::GetBodyUsed(ErrorResult& aRv) const {
+ if (mBodyUsed) {
+ return true;
+ }
+
+ // If this stream is disturbed, return true.
+ if (mReadableStreamBody) {
+ aRv.MightThrowJSException();
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mOwner)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return true;
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> body(cx, mReadableStreamBody);
+ bool disturbed;
+ if (!JS::ReadableStreamIsDisturbed(cx, body, &disturbed)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return false;
+ }
+
+ return disturbed;
+ }
+
+ return false;
+}
+
+template bool FetchBody<Request>::GetBodyUsed(ErrorResult&) const;
+
+template bool FetchBody<Response>::GetBodyUsed(ErrorResult&) const;
+
+template <class Derived>
+bool FetchBody<Derived>::CheckBodyUsed() const {
+ IgnoredErrorResult result;
+ bool bodyUsed = GetBodyUsed(result);
+ if (result.Failed()) {
+ // Ignore the error.
+ return true;
+ }
+ return bodyUsed;
+}
+
+template <class Derived>
+void FetchBody<Derived>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(mOwner->EventTargetFor(TaskCategory::Other)->IsOnCurrentThread());
+
+ if (mBodyUsed) {
+ return;
+ }
+
+ mBodyUsed = true;
+
+ // If we already have a ReadableStreamBody and it has been created by DOM, we
+ // have to lock it now because it can have been shared with other objects.
+ if (mReadableStreamBody) {
+ aRv.MightThrowJSException();
+
+ JSAutoRealm ar(aCx, mOwner->GetGlobalJSObject());
+
+ JS::Rooted<JSObject*> readableStreamObj(aCx, mReadableStreamBody);
+
+ JS::ReadableStreamMode mode;
+ if (!JS::ReadableStreamGetMode(aCx, readableStreamObj, &mode)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ if (mode == JS::ReadableStreamMode::ExternalSource) {
+ LockStream(aCx, readableStreamObj, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ } else {
+ // If this is not a native ReadableStream, let's activate the
+ // FetchStreamReader.
+ MOZ_ASSERT(mFetchStreamReader);
+ JS::Rooted<JSObject*> reader(aCx);
+ mFetchStreamReader->StartConsuming(aCx, readableStreamObj, &reader, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ mReadableStreamReader = reader;
+ }
+ }
+}
+
+template void FetchBody<Request>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
+
+template void FetchBody<Response>::SetBodyUsed(JSContext* aCx,
+ ErrorResult& aRv);
+
+template <class Derived>
+already_AddRefed<Promise> FetchBody<Derived>::ConsumeBody(
+ JSContext* aCx, BodyConsumer::ConsumeType aType, ErrorResult& aRv) {
+ aRv.MightThrowJSException();
+
+ RefPtr<AbortSignalImpl> signalImpl = DerivedClass()->GetSignalImpl();
+ if (signalImpl && signalImpl->Aborted()) {
+ aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+ return nullptr;
+ }
+
+ bool bodyUsed = GetBodyUsed(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ if (bodyUsed) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ nsAutoCString mimeType;
+ DerivedClass()->GetMimeType(mimeType);
+
+ // Null bodies are a special-case in the fetch spec. The Body mix-in can only
+ // be "disturbed" or "locked" if its associated "body" is non-null.
+ // Additionally, the Body min-in's "consume body" algorithm explicitly creates
+ // a fresh empty ReadableStream object in step 2. This means that `bodyUsed`
+ // will never return true for a null body.
+ //
+ // To this end, we create a fresh (empty) body every time a request is made
+ // and consume its body here, without marking this FetchBody consumed via
+ // SetBodyUsed.
+ nsCOMPtr<nsIInputStream> bodyStream;
+ DerivedClass()->GetBody(getter_AddRefs(bodyStream));
+ if (!bodyStream) {
+ RefPtr<EmptyBody> emptyBody = EmptyBody::Create(
+ DerivedClass()->GetParentObject(),
+ DerivedClass()->GetPrincipalInfo().get(), signalImpl, mimeType, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return emptyBody->ConsumeBody(aCx, aType, aRv);
+ }
+
+ SetBodyUsed(aCx, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = DerivedClass()->GetParentObject();
+
+ MutableBlobStorage::MutableBlobStorageType blobStorageType =
+ MutableBlobStorage::eOnlyInMemory;
+ const mozilla::UniquePtr<mozilla::ipc::PrincipalInfo>& principalInfo =
+ DerivedClass()->GetPrincipalInfo();
+ // We support temporary file for blobs only if the principal is known and
+ // it's system or content not in private Browsing.
+ if (principalInfo &&
+ (principalInfo->type() ==
+ mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo ||
+ (principalInfo->type() ==
+ mozilla::ipc::PrincipalInfo::TContentPrincipalInfo &&
+ principalInfo->get_ContentPrincipalInfo().attrs().mPrivateBrowsingId ==
+ 0))) {
+ blobStorageType = MutableBlobStorage::eCouldBeInTemporaryFile;
+ }
+
+ RefPtr<Promise> promise = BodyConsumer::Create(
+ global, mMainThreadEventTarget, bodyStream, signalImpl, aType,
+ BodyBlobURISpec(), BodyLocalPath(), mimeType, blobStorageType, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return promise.forget();
+}
+
+template already_AddRefed<Promise> FetchBody<Request>::ConsumeBody(
+ JSContext* aCx, BodyConsumer::ConsumeType aType, ErrorResult& aRv);
+
+template already_AddRefed<Promise> FetchBody<Response>::ConsumeBody(
+ JSContext* aCx, BodyConsumer::ConsumeType aType, ErrorResult& aRv);
+
+template already_AddRefed<Promise> FetchBody<EmptyBody>::ConsumeBody(
+ JSContext* aCx, BodyConsumer::ConsumeType aType, ErrorResult& aRv);
+
+template <class Derived>
+void FetchBody<Derived>::GetMimeType(nsACString& aMimeType) {
+ // Extract mime type.
+ ErrorResult result;
+ nsCString contentTypeValues;
+ MOZ_ASSERT(DerivedClass()->GetInternalHeaders());
+ DerivedClass()->GetInternalHeaders()->Get("Content-Type"_ns,
+ contentTypeValues, result);
+ MOZ_ALWAYS_TRUE(!result.Failed());
+
+ // HTTP ABNF states Content-Type may have only one value.
+ // This is from the "parse a header value" of the fetch spec.
+ if (!contentTypeValues.IsVoid() && contentTypeValues.Find(",") == -1) {
+ // Convert from a bytestring to a UTF8 CString.
+ CopyLatin1toUTF8(contentTypeValues, aMimeType);
+ ToLowerCase(aMimeType);
+ }
+}
+
+template void FetchBody<Request>::GetMimeType(nsACString& aMimeType);
+template void FetchBody<Response>::GetMimeType(nsACString& aMimeType);
+
+template <class Derived>
+const nsACString& FetchBody<Derived>::BodyBlobURISpec() const {
+ return DerivedClass()->BodyBlobURISpec();
+}
+
+template const nsACString& FetchBody<Request>::BodyBlobURISpec() const;
+
+template const nsACString& FetchBody<Response>::BodyBlobURISpec() const;
+
+template const nsACString& FetchBody<EmptyBody>::BodyBlobURISpec() const;
+
+template <class Derived>
+const nsAString& FetchBody<Derived>::BodyLocalPath() const {
+ return DerivedClass()->BodyLocalPath();
+}
+
+template const nsAString& FetchBody<Request>::BodyLocalPath() const;
+
+template const nsAString& FetchBody<Response>::BodyLocalPath() const;
+
+template const nsAString& FetchBody<EmptyBody>::BodyLocalPath() const;
+
+template <class Derived>
+void FetchBody<Derived>::SetReadableStreamBody(JSContext* aCx,
+ JSObject* aBody) {
+ MOZ_ASSERT(!mReadableStreamBody);
+ MOZ_ASSERT(aBody);
+ mReadableStreamBody = aBody;
+
+ RefPtr<AbortSignalImpl> signalImpl = DerivedClass()->GetSignalImpl();
+ if (!signalImpl) {
+ return;
+ }
+
+ bool aborted = signalImpl->Aborted();
+ if (aborted) {
+ JS::Rooted<JSObject*> body(aCx, mReadableStreamBody);
+ IgnoredErrorResult result;
+ AbortStream(aCx, body, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return;
+ }
+ } else if (!IsFollowing()) {
+ Follow(signalImpl);
+ }
+}
+
+template void FetchBody<Request>::SetReadableStreamBody(JSContext* aCx,
+ JSObject* aBody);
+
+template void FetchBody<Response>::SetReadableStreamBody(JSContext* aCx,
+ JSObject* aBody);
+
+template <class Derived>
+void FetchBody<Derived>::GetBody(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aBodyOut,
+ ErrorResult& aRv) {
+ if (mReadableStreamBody) {
+ aBodyOut.set(mReadableStreamBody);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ DerivedClass()->GetBody(getter_AddRefs(inputStream));
+
+ if (!inputStream) {
+ aBodyOut.set(nullptr);
+ return;
+ }
+
+ BodyStream::Create(aCx, this, DerivedClass()->GetParentObject(), inputStream,
+ aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(mReadableStreamBody);
+
+ JS::Rooted<JSObject*> body(aCx, mReadableStreamBody);
+
+ // If the body has been already consumed, we lock the stream.
+ bool bodyUsed = GetBodyUsed(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ if (bodyUsed) {
+ LockStream(aCx, body, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ RefPtr<AbortSignalImpl> signalImpl = DerivedClass()->GetSignalImpl();
+ if (signalImpl) {
+ if (signalImpl->Aborted()) {
+ AbortStream(aCx, body, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ } else if (!IsFollowing()) {
+ Follow(signalImpl);
+ }
+ }
+
+ aBodyOut.set(mReadableStreamBody);
+}
+
+template void FetchBody<Request>::GetBody(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aMessage,
+ ErrorResult& aRv);
+
+template void FetchBody<Response>::GetBody(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aMessage, ErrorResult& aRv);
+
+template <class Derived>
+void FetchBody<Derived>::LockStream(JSContext* aCx, JS::HandleObject aStream,
+ ErrorResult& aRv) {
+ aRv.MightThrowJSException();
+
+#if DEBUG
+ JS::ReadableStreamMode streamMode;
+ if (!JS::ReadableStreamGetMode(aCx, aStream, &streamMode)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ MOZ_ASSERT(streamMode == JS::ReadableStreamMode::ExternalSource);
+#endif // DEBUG
+
+ // This is native stream, creating a reader will not execute any JS code.
+ JS::Rooted<JSObject*> reader(
+ aCx, JS::ReadableStreamGetReader(aCx, aStream,
+ JS::ReadableStreamReaderMode::Default));
+ if (!reader) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ mReadableStreamReader = reader;
+}
+
+template void FetchBody<Request>::LockStream(JSContext* aCx,
+ JS::HandleObject aStream,
+ ErrorResult& aRv);
+
+template void FetchBody<Response>::LockStream(JSContext* aCx,
+ JS::HandleObject aStream,
+ ErrorResult& aRv);
+
+template <class Derived>
+void FetchBody<Derived>::MaybeTeeReadableStreamBody(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aBodyOut,
+ FetchStreamReader** aStreamReader, nsIInputStream** aInputStream,
+ ErrorResult& aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(aStreamReader);
+ MOZ_DIAGNOSTIC_ASSERT(aInputStream);
+ MOZ_DIAGNOSTIC_ASSERT(!CheckBodyUsed());
+
+ aBodyOut.set(nullptr);
+ *aStreamReader = nullptr;
+ *aInputStream = nullptr;
+
+ if (!mReadableStreamBody) {
+ return;
+ }
+
+ aRv.MightThrowJSException();
+
+ JSAutoRealm ar(aCx, mOwner->GetGlobalJSObject());
+
+ JS::Rooted<JSObject*> stream(aCx, mReadableStreamBody);
+
+ // If this is a ReadableStream with an external source, this has been
+ // generated by a Fetch. In this case, Fetch will be able to recreate it
+ // again when GetBody() is called.
+ JS::ReadableStreamMode streamMode;
+ if (!JS::ReadableStreamGetMode(aCx, stream, &streamMode)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ if (streamMode == JS::ReadableStreamMode::ExternalSource) {
+ aBodyOut.set(nullptr);
+ return;
+ }
+
+ JS::Rooted<JSObject*> branch1(aCx);
+ JS::Rooted<JSObject*> branch2(aCx);
+
+ if (!JS::ReadableStreamTee(aCx, stream, &branch1, &branch2)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ mReadableStreamBody = branch1;
+ aBodyOut.set(branch2);
+
+ aRv = FetchStreamReader::Create(aCx, mOwner, aStreamReader, aInputStream);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+}
+
+template void FetchBody<Request>::MaybeTeeReadableStreamBody(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aMessage,
+ FetchStreamReader** aStreamReader, nsIInputStream** aInputStream,
+ ErrorResult& aRv);
+
+template void FetchBody<Response>::MaybeTeeReadableStreamBody(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aMessage,
+ FetchStreamReader** aStreamReader, nsIInputStream** aInputStream,
+ ErrorResult& aRv);
+
+template <class Derived>
+void FetchBody<Derived>::RunAbortAlgorithm() {
+ if (!mReadableStreamBody) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mOwner)) {
+ return;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> body(cx, mReadableStreamBody);
+ IgnoredErrorResult result;
+ AbortStream(cx, body, result);
+}
+
+template void FetchBody<Request>::RunAbortAlgorithm();
+
+template void FetchBody<Response>::RunAbortAlgorithm();
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/Fetch.h b/dom/fetch/Fetch.h
new file mode 100644
index 0000000000..1689848be7
--- /dev/null
+++ b/dom/fetch/Fetch.h
@@ -0,0 +1,262 @@
+/* -*- 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_Fetch_h
+#define mozilla_dom_Fetch_h
+
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsProxyRelease.h"
+#include "nsString.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/AbortSignal.h"
+#include "mozilla/dom/BodyConsumer.h"
+#include "mozilla/dom/BodyStream.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/FetchStreamReader.h"
+#include "mozilla/dom/RequestBinding.h"
+
+class nsIGlobalObject;
+class nsIEventTarget;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
+class
+ BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString;
+class BlobImpl;
+class InternalRequest;
+class
+ OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
+struct ReadableStream;
+class RequestOrUSVString;
+class WorkerPrivate;
+
+enum class CallerType : uint32_t;
+
+already_AddRefed<Promise> FetchRequest(nsIGlobalObject* aGlobal,
+ const RequestOrUSVString& aInput,
+ const RequestInit& aInit,
+ CallerType aCallerType,
+ ErrorResult& aRv);
+
+nsresult UpdateRequestReferrer(nsIGlobalObject* aGlobal,
+ InternalRequest* aRequest);
+
+namespace fetch {
+typedef BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString
+ BodyInit;
+typedef BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString
+ ResponseBodyInit;
+typedef OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString
+ OwningBodyInit;
+}; // namespace fetch
+
+/*
+ * Creates an nsIInputStream based on the fetch specifications 'extract a byte
+ * stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract.
+ * Stores content type in out param aContentType.
+ */
+nsresult ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit,
+ nsIInputStream** aStream,
+ nsCString& aContentType,
+ uint64_t& aContentLength);
+
+/*
+ * Non-owning version.
+ */
+nsresult ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit,
+ nsIInputStream** aStream,
+ nsCString& aContentType,
+ uint64_t& aContentLength);
+
+/*
+ * Non-owning version. This method should go away when BodyInit will contain
+ * ReadableStream.
+ */
+nsresult ExtractByteStreamFromBody(const fetch::ResponseBodyInit& aBodyInit,
+ nsIInputStream** aStream,
+ nsCString& aContentType,
+ uint64_t& aContentLength);
+
+/*
+ * FetchBody's body consumption uses nsIInputStreamPump to read from the
+ * underlying stream to a block of memory, which is then adopted by
+ * ContinueConsumeBody() and converted to the right type based on the JS
+ * function called.
+ *
+ * Use of the nsIInputStreamPump complicates things on the worker thread.
+ * The solution used here is similar to WebSockets.
+ * The difference is that we are only interested in completion and not data
+ * events, and nsIInputStreamPump can only deliver completion on the main
+ * thread.
+ *
+ * Before starting the pump on the main thread, we addref the FetchBody to keep
+ * it alive. Then we add a feature, to track the status of the worker.
+ *
+ * ContinueConsumeBody() is the function that cleans things up in both success
+ * and error conditions and so all callers call it with the appropriate status.
+ *
+ * Once the read is initiated on the main thread there are two possibilities.
+ *
+ * 1) Pump finishes before worker has finished Running.
+ * In this case we adopt the data and dispatch a runnable to the worker,
+ * which derefs FetchBody and removes the feature and resolves the Promise.
+ *
+ * 2) Pump still working while worker has stopped Running.
+ * The feature is Notify()ed and ContinueConsumeBody() is called with
+ * NS_BINDING_ABORTED. We first Cancel() the pump using a sync runnable to
+ * ensure that mFetchBody remains alive (since mConsumeBodyPump is strongly
+ * held by it) until pump->Cancel() is called. OnStreamComplete() will not
+ * do anything if the error code is NS_BINDING_ABORTED, so we don't have to
+ * worry about keeping anything alive.
+ *
+ * The pump is always released on the main thread.
+ */
+template <class Derived>
+class FetchBody : public BodyStreamHolder, public AbortFollower {
+ public:
+ using BodyStreamHolder::QueryInterface;
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(FetchBody, BodyStreamHolder)
+
+ bool GetBodyUsed(ErrorResult& aRv) const;
+
+ // For use in assertions. On success, returns true if the body is used, false
+ // if not. On error, this sweeps the error under the rug and returns true.
+ bool CheckBodyUsed() const;
+
+ already_AddRefed<Promise> ArrayBuffer(JSContext* aCx, ErrorResult& aRv) {
+ return ConsumeBody(aCx, BodyConsumer::CONSUME_ARRAYBUFFER, aRv);
+ }
+
+ already_AddRefed<Promise> Blob(JSContext* aCx, ErrorResult& aRv) {
+ return ConsumeBody(aCx, BodyConsumer::CONSUME_BLOB, aRv);
+ }
+
+ already_AddRefed<Promise> FormData(JSContext* aCx, ErrorResult& aRv) {
+ return ConsumeBody(aCx, BodyConsumer::CONSUME_FORMDATA, aRv);
+ }
+
+ already_AddRefed<Promise> Json(JSContext* aCx, ErrorResult& aRv) {
+ return ConsumeBody(aCx, BodyConsumer::CONSUME_JSON, aRv);
+ }
+
+ already_AddRefed<Promise> Text(JSContext* aCx, ErrorResult& aRv) {
+ return ConsumeBody(aCx, BodyConsumer::CONSUME_TEXT, aRv);
+ }
+
+ void GetBody(JSContext* aCx, JS::MutableHandle<JSObject*> aBodyOut,
+ ErrorResult& aRv);
+
+ void GetMimeType(nsACString& aMimeType);
+
+ const nsACString& BodyBlobURISpec() const;
+
+ const nsAString& BodyLocalPath() const;
+
+ // If the body contains a ReadableStream body object, this method produces a
+ // tee() of it.
+ void MaybeTeeReadableStreamBody(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aBodyOut,
+ FetchStreamReader** aStreamReader,
+ nsIInputStream** aInputStream,
+ ErrorResult& aRv);
+
+ // Utility public methods accessed by various runnables.
+
+ // This method _must_ be called in order to set the body as used. If the body
+ // is a ReadableStream, this method will start reading the stream.
+ // More in details, this method does:
+ // 1) It uses an internal flag to track if the body is used. This is tracked
+ // separately from the ReadableStream disturbed state due to purely native
+ // streams.
+ // 2) If there is a ReadableStream reflector for the native stream it is
+ // Locked.
+ // 3) If there is a JS ReadableStream then we begin pumping it into the native
+ // body stream. This effectively locks and disturbs the stream.
+ //
+ // Note that JSContext is used only if there is a ReadableStream (this can
+ // happen because the body is a ReadableStream or because attribute body has
+ // already been used by content). If something goes wrong using
+ // ReadableStream, errors will be reported via ErrorResult and not as JS
+ // exceptions in JSContext. This is done in order to have a centralized error
+ // reporting way.
+ //
+ // Exceptions generated when reading from the ReadableStream are directly sent
+ // to the Console.
+ void SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
+
+ // BodyStreamHolder
+ void NullifyStream() override {
+ mReadableStreamBody = nullptr;
+ mReadableStreamReader = nullptr;
+ mFetchStreamReader = nullptr;
+ }
+
+ void SetReadableStreamBody(JSObject* aBody) override {
+ mReadableStreamBody = aBody;
+ }
+
+ JSObject* GetReadableStreamBody() override { return mReadableStreamBody; }
+
+ void MarkAsRead() override { mBodyUsed = true; }
+
+ virtual AbortSignalImpl* GetSignalImpl() const = 0;
+
+ // AbortFollower
+ void RunAbortAlgorithm() override;
+
+ already_AddRefed<Promise> ConsumeBody(JSContext* aCx,
+ BodyConsumer::ConsumeType aType,
+ ErrorResult& aRv);
+
+ protected:
+ nsCOMPtr<nsIGlobalObject> mOwner;
+
+ // Always set whenever the FetchBody is created on the worker thread.
+ WorkerPrivate* mWorkerPrivate;
+
+ // This is the ReadableStream exposed to content. It's underlying source is a
+ // BodyStream object.
+ JS::Heap<JSObject*> mReadableStreamBody;
+
+ // This is the Reader used to retrieve data from the body.
+ JS::Heap<JSObject*> mReadableStreamReader;
+ RefPtr<FetchStreamReader> mFetchStreamReader;
+
+ explicit FetchBody(nsIGlobalObject* aOwner);
+
+ virtual ~FetchBody();
+
+ void SetReadableStreamBody(JSContext* aCx, JSObject* aBody);
+
+ private:
+ Derived* DerivedClass() const {
+ return static_cast<Derived*>(const_cast<FetchBody*>(this));
+ }
+
+ void LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv);
+
+ bool IsOnTargetThread() { return NS_IsMainThread() == !mWorkerPrivate; }
+
+ void AssertIsOnTargetThread() { MOZ_ASSERT(IsOnTargetThread()); }
+
+ // Only ever set once, always on target thread.
+ bool mBodyUsed;
+
+ // The main-thread event target for runnable dispatching.
+ nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Fetch_h
diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp
new file mode 100644
index 0000000000..31bc483d34
--- /dev/null
+++ b/dom/fetch/FetchDriver.cpp
@@ -0,0 +1,1601 @@
+/* -*- 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 "mozilla/DebugOnly.h"
+#include "mozilla/dom/FetchDriver.h"
+
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "mozilla/dom/Document.h"
+#include "nsICookieJarSettings.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIFileChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsISupportsPriority.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIUploadChannel2.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPipe.h"
+
+#include "nsContentPolicyUtils.h"
+#include "nsDataHandler.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsProxyRelease.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsHttpChannel.h"
+
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/dom/UserActivation.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/PreloaderBase.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/Unused.h"
+
+#include "Fetch.h"
+#include "FetchUtil.h"
+#include "InternalRequest.h"
+#include "InternalResponse.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+void GetBlobURISpecFromChannel(nsIRequest* aRequest, nsCString& aBlobURISpec) {
+ MOZ_ASSERT(aRequest);
+
+ aBlobURISpec.SetIsVoid(true);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (!channel) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = channel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (!dom::IsBlobURI(uri)) {
+ return;
+ }
+
+ uri->GetSpec(aBlobURISpec);
+}
+
+bool ShouldCheckSRI(const InternalRequest& aRequest,
+ const InternalResponse& aResponse) {
+ return !aRequest.GetIntegrity().IsEmpty() &&
+ aResponse.Type() != ResponseType::Error;
+}
+
+} // anonymous namespace
+
+//-----------------------------------------------------------------------------
+// AlternativeDataStreamListener
+//-----------------------------------------------------------------------------
+class AlternativeDataStreamListener final
+ : public nsIStreamListener,
+ public nsIThreadRetargetableStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ // The status of AlternativeDataStreamListener
+ // LOADING: is the initial status, loading the alternative data
+ // COMPLETED: Alternative data loading is completed
+ // CANCELED: Alternative data loading is canceled, this would make
+ // AlternativeDataStreamListener ignore all channel callbacks
+ // FALLBACK: fallback the channel callbacks to FetchDriver
+ // Depends on different situaions, the status transition could be followings
+ // 1. LOADING->COMPLETED
+ // This is the normal status transition for alternative data loading
+ //
+ // 2. LOADING->CANCELED
+ // LOADING->COMPLETED->CANCELED
+ // Alternative data loading could be canceled when cacheId from alternative
+ // data channel does not match with from main data channel(The cacheID
+ // checking is in FetchDriver::OnStartRequest).
+ // Notice the alternative data loading could finish before the cacheID
+ // checking, so the statust transition could be
+ // LOADING->COMPLETED->CANCELED
+ //
+ // 3. LOADING->FALLBACK
+ // For the case that alternative data loading could not be initialized,
+ // i.e. alternative data does not exist or no preferred alternative data
+ // type is requested. Once the status becomes FALLBACK,
+ // AlternativeDataStreamListener transits the channel callback request to
+ // FetchDriver, and the status should not go back to LOADING, COMPLETED, or
+ // CANCELED anymore.
+ enum eStatus { LOADING = 0, COMPLETED, CANCELED, FALLBACK };
+
+ AlternativeDataStreamListener(FetchDriver* aFetchDriver, nsIChannel* aChannel,
+ const nsACString& aAlternativeDataType);
+ eStatus Status();
+ void Cancel();
+ uint64_t GetAlternativeDataCacheEntryId();
+ const nsACString& GetAlternativeDataType() const;
+ already_AddRefed<nsICacheInfoChannel> GetCacheInfoChannel();
+ already_AddRefed<nsIInputStream> GetAlternativeInputStream();
+
+ private:
+ ~AlternativeDataStreamListener() = default;
+
+ // This creates a strong reference cycle with FetchDriver and its
+ // mAltDataListener. We need to clear at least one reference of them once the
+ // data loading finishes.
+ RefPtr<FetchDriver> mFetchDriver;
+ nsCString mAlternativeDataType;
+ nsCOMPtr<nsIInputStream> mPipeAlternativeInputStream;
+ nsCOMPtr<nsIOutputStream> mPipeAlternativeOutputStream;
+ uint64_t mAlternativeDataCacheEntryId;
+ nsCOMPtr<nsICacheInfoChannel> mCacheInfoChannel;
+ nsCOMPtr<nsIChannel> mChannel;
+ Atomic<eStatus> mStatus;
+};
+
+NS_IMPL_ISUPPORTS(AlternativeDataStreamListener, nsIStreamListener,
+ nsIThreadRetargetableStreamListener)
+
+AlternativeDataStreamListener::AlternativeDataStreamListener(
+ FetchDriver* aFetchDriver, nsIChannel* aChannel,
+ const nsACString& aAlternativeDataType)
+ : mFetchDriver(aFetchDriver),
+ mAlternativeDataType(aAlternativeDataType),
+ mAlternativeDataCacheEntryId(0),
+ mChannel(aChannel),
+ mStatus(AlternativeDataStreamListener::LOADING) {
+ MOZ_DIAGNOSTIC_ASSERT(mFetchDriver);
+ MOZ_DIAGNOSTIC_ASSERT(mChannel);
+}
+
+AlternativeDataStreamListener::eStatus AlternativeDataStreamListener::Status() {
+ return mStatus;
+}
+
+void AlternativeDataStreamListener::Cancel() {
+ mAlternativeDataCacheEntryId = 0;
+ mCacheInfoChannel = nullptr;
+ mPipeAlternativeOutputStream = nullptr;
+ mPipeAlternativeInputStream = nullptr;
+ if (mChannel && mStatus != AlternativeDataStreamListener::FALLBACK) {
+ // if mStatus is fallback, we need to keep channel to forward request back
+ // to FetchDriver
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ mChannel = nullptr;
+ }
+ mStatus = AlternativeDataStreamListener::CANCELED;
+}
+
+uint64_t AlternativeDataStreamListener::GetAlternativeDataCacheEntryId() {
+ return mAlternativeDataCacheEntryId;
+}
+
+const nsACString& AlternativeDataStreamListener::GetAlternativeDataType()
+ const {
+ return mAlternativeDataType;
+}
+
+already_AddRefed<nsIInputStream>
+AlternativeDataStreamListener::GetAlternativeInputStream() {
+ nsCOMPtr<nsIInputStream> inputStream = mPipeAlternativeInputStream;
+ return inputStream.forget();
+}
+
+already_AddRefed<nsICacheInfoChannel>
+AlternativeDataStreamListener::GetCacheInfoChannel() {
+ nsCOMPtr<nsICacheInfoChannel> channel = mCacheInfoChannel;
+ return channel.forget();
+}
+
+NS_IMETHODIMP
+AlternativeDataStreamListener::OnStartRequest(nsIRequest* aRequest) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mAlternativeDataType.IsEmpty());
+ // Checking the alternative data type is the same between we asked and the
+ // saved in the channel.
+ nsAutoCString alternativeDataType;
+ nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(aRequest);
+ mStatus = AlternativeDataStreamListener::LOADING;
+ if (cic && NS_SUCCEEDED(cic->GetAlternativeDataType(alternativeDataType)) &&
+ mAlternativeDataType.Equals(alternativeDataType) &&
+ NS_SUCCEEDED(cic->GetCacheEntryId(&mAlternativeDataCacheEntryId))) {
+ MOZ_DIAGNOSTIC_ASSERT(!mPipeAlternativeInputStream);
+ MOZ_DIAGNOSTIC_ASSERT(!mPipeAlternativeOutputStream);
+ nsresult rv =
+ NS_NewPipe(getter_AddRefs(mPipeAlternativeInputStream),
+ getter_AddRefs(mPipeAlternativeOutputStream),
+ 0 /* default segment size */, UINT32_MAX /* infinite pipe */,
+ true /* non-blocking input, otherwise you deadlock */,
+ false /* blocking output, since the pipe is 'in'finite */);
+
+ if (NS_FAILED(rv)) {
+ mFetchDriver->FailWithNetworkError(rv);
+ return rv;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!mCacheInfoChannel);
+ mCacheInfoChannel = cic;
+
+ // call FetchDriver::HttpFetch to load main body
+ MOZ_ASSERT(mFetchDriver);
+ return mFetchDriver->HttpFetch();
+
+ } else {
+ // Needn't load alternative data, since alternative data does not exist.
+ // Set status to FALLBACK to reuse the opened channel to load main body,
+ // then call FetchDriver::OnStartRequest to continue the work. Unfortunately
+ // can't change the stream listener to mFetchDriver, need to keep
+ // AlternativeDataStreamListener alive to redirect OnDataAvailable and
+ // OnStopRequest to mFetchDriver.
+ MOZ_ASSERT(alternativeDataType.IsEmpty());
+ mStatus = AlternativeDataStreamListener::FALLBACK;
+ mAlternativeDataCacheEntryId = 0;
+ MOZ_ASSERT(mFetchDriver);
+ return mFetchDriver->OnStartRequest(aRequest);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlternativeDataStreamListener::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount) {
+ if (mStatus == AlternativeDataStreamListener::LOADING) {
+ MOZ_ASSERT(mPipeAlternativeOutputStream);
+ uint32_t read = 0;
+ return aInputStream->ReadSegments(
+ NS_CopySegmentToStream, mPipeAlternativeOutputStream, aCount, &read);
+ }
+ if (mStatus == AlternativeDataStreamListener::FALLBACK) {
+ MOZ_ASSERT(mFetchDriver);
+ return mFetchDriver->OnDataAvailable(aRequest, aInputStream, aOffset,
+ aCount);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlternativeDataStreamListener::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ AssertIsOnMainThread();
+
+ // Alternative data loading is going to finish, breaking the reference cycle
+ // here by taking the ownership to a loacl variable.
+ RefPtr<FetchDriver> fetchDriver = std::move(mFetchDriver);
+
+ if (mStatus == AlternativeDataStreamListener::CANCELED) {
+ // do nothing
+ return NS_OK;
+ }
+
+ if (mStatus == AlternativeDataStreamListener::FALLBACK) {
+ MOZ_ASSERT(fetchDriver);
+ return fetchDriver->OnStopRequest(aRequest, aStatusCode);
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mStatus == AlternativeDataStreamListener::LOADING);
+
+ MOZ_ASSERT(!mAlternativeDataType.IsEmpty() && mPipeAlternativeOutputStream &&
+ mPipeAlternativeInputStream);
+
+ mPipeAlternativeOutputStream->Close();
+ mPipeAlternativeOutputStream = nullptr;
+
+ // Cleanup the states for alternative data if needed.
+ if (NS_FAILED(aStatusCode)) {
+ mAlternativeDataCacheEntryId = 0;
+ mCacheInfoChannel = nullptr;
+ mPipeAlternativeInputStream = nullptr;
+ }
+ mStatus = AlternativeDataStreamListener::COMPLETED;
+ // alternative data loading finish, call FetchDriver::FinishOnStopRequest to
+ // continue the final step for the case FetchDriver::OnStopRequest is called
+ // earlier than AlternativeDataStreamListener::OnStopRequest
+ MOZ_ASSERT(fetchDriver);
+ fetchDriver->FinishOnStopRequest(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlternativeDataStreamListener::CheckListenerChain() { return NS_OK; }
+//-----------------------------------------------------------------------------
+// FetchDriver
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(FetchDriver, nsIStreamListener, nsIChannelEventSink,
+ nsIInterfaceRequestor, nsIThreadRetargetableStreamListener)
+
+FetchDriver::FetchDriver(SafeRefPtr<InternalRequest> aRequest,
+ nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup,
+ nsIEventTarget* aMainThreadEventTarget,
+ nsICookieJarSettings* aCookieJarSettings,
+ PerformanceStorage* aPerformanceStorage,
+ bool aIsTrackingFetch)
+ : mPrincipal(aPrincipal),
+ mLoadGroup(aLoadGroup),
+ mRequest(std::move(aRequest)),
+ mMainThreadEventTarget(aMainThreadEventTarget),
+ mCookieJarSettings(aCookieJarSettings),
+ mPerformanceStorage(aPerformanceStorage),
+ mNeedToObserveOnDataAvailable(false),
+ mIsTrackingFetch(aIsTrackingFetch),
+ mOnStopRequestCalled(false)
+#ifdef DEBUG
+ ,
+ mResponseAvailableCalled(false),
+ mFetchCalled(false)
+#endif
+{
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(mRequest);
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aMainThreadEventTarget);
+}
+
+FetchDriver::~FetchDriver() {
+ AssertIsOnMainThread();
+
+ // We assert this since even on failures, we should call
+ // FailWithNetworkError().
+ MOZ_ASSERT(mResponseAvailableCalled);
+}
+
+already_AddRefed<PreloaderBase> FetchDriver::FindPreload(nsIURI* aURI) {
+ // Decide if we allow reuse of an existing <link rel=preload as=fetch>
+ // response for this request. First examine this fetch requets itself if it
+ // is 'pure' enough to use the response and then try to find a preload.
+
+ if (!mDocument) {
+ // Preloads are mapped on the document, no document, no preload.
+ return nullptr;
+ }
+ CORSMode cors;
+ switch (mRequest->Mode()) {
+ case RequestMode::No_cors:
+ cors = CORSMode::CORS_NONE;
+ break;
+ case RequestMode::Cors:
+ cors = mRequest->GetCredentialsMode() == RequestCredentials::Include
+ ? CORSMode::CORS_USE_CREDENTIALS
+ : CORSMode::CORS_ANONYMOUS;
+ break;
+ default:
+ // Can't be satisfied by a preload because preload cannot define any of
+ // remaining modes.
+ return nullptr;
+ }
+ if (!mRequest->Headers()->HasOnlySimpleHeaders()) {
+ // Preload can't set any headers.
+ return nullptr;
+ }
+ if (!mRequest->GetIntegrity().IsEmpty()) {
+ // There is currently no support for SRI checking in the fetch preloader.
+ return nullptr;
+ }
+ if (mRequest->GetCacheMode() != RequestCache::Default) {
+ // Preload can only go with the default caching mode.
+ return nullptr;
+ }
+ if (mRequest->SkipServiceWorker()) {
+ // Preload can't be forbidden interception.
+ return nullptr;
+ }
+ if (mRequest->GetRedirectMode() != RequestRedirect::Follow) {
+ // Preload always follows redirects.
+ return nullptr;
+ }
+ nsAutoCString method;
+ mRequest->GetMethod(method);
+ if (!method.EqualsLiteral("GET")) {
+ // Preload can only do GET, this also eliminates the case we do upload, so
+ // no need to check if the request has any body to send out.
+ return nullptr;
+ }
+
+ // OK, this request can be satisfied by a preloaded response, try to find one.
+
+ auto preloadKey = PreloadHashKey::CreateAsFetch(aURI, cors);
+ return mDocument->Preloads().LookupPreload(preloadKey);
+}
+
+void FetchDriver::UpdateReferrerInfoFromNewChannel(nsIChannel* aChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (!httpChannel) {
+ return;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
+ if (!referrerInfo) {
+ return;
+ }
+
+ nsAutoString computedReferrerSpec;
+ mRequest->SetReferrerPolicy(referrerInfo->ReferrerPolicy());
+ Unused << referrerInfo->GetComputedReferrerSpec(computedReferrerSpec);
+ mRequest->SetReferrer(computedReferrerSpec);
+}
+
+nsresult FetchDriver::Fetch(AbortSignalImpl* aSignalImpl,
+ FetchDriverObserver* aObserver) {
+ AssertIsOnMainThread();
+#ifdef DEBUG
+ MOZ_ASSERT(!mFetchCalled);
+ mFetchCalled = true;
+#endif
+
+ mObserver = aObserver;
+
+ // FIXME(nsm): Deal with HSTS.
+
+ MOZ_RELEASE_ASSERT(!mRequest->IsSynchronous(),
+ "Synchronous fetch not supported");
+
+ UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo(
+ new mozilla::ipc::PrincipalInfo());
+ nsresult rv = PrincipalToPrincipalInfo(mPrincipal, principalInfo.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mRequest->SetPrincipalInfo(std::move(principalInfo));
+
+ // If the signal is aborted, it's time to inform the observer and terminate
+ // the operation.
+ if (aSignalImpl) {
+ if (aSignalImpl->Aborted()) {
+ RunAbortAlgorithm();
+ return NS_OK;
+ }
+
+ Follow(aSignalImpl);
+ }
+
+ rv = HttpFetch(mRequest->GetPreferredAlternativeDataType());
+ if (NS_FAILED(rv)) {
+ FailWithNetworkError(rv);
+ }
+
+ // Any failure is handled by FailWithNetworkError notifying the aObserver.
+ return NS_OK;
+}
+
+// This function implements the "HTTP Fetch" algorithm from the Fetch spec.
+// Functionality is often split between here, the CORS listener proxy and the
+// Necko HTTP implementation.
+nsresult FetchDriver::HttpFetch(
+ const nsACString& aPreferredAlternativeDataType) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Step 1. "Let response be null."
+ mResponse = nullptr;
+ mOnStopRequestCalled = false;
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString url;
+ mRequest->GetURL(url);
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Unsafe requests aren't allowed with when using no-core mode.
+ if (mRequest->Mode() == RequestMode::No_cors && mRequest->UnsafeRequest() &&
+ (!mRequest->HasSimpleMethod() ||
+ !mRequest->Headers()->HasOnlySimpleHeaders())) {
+ MOZ_ASSERT(false, "The API should have caught this");
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ // non-GET requests aren't allowed for blob.
+ if (IsBlobURI(uri)) {
+ nsAutoCString method;
+ mRequest->GetMethod(method);
+ if (!method.EqualsLiteral("GET")) {
+ return NS_ERROR_DOM_NETWORK_ERR;
+ }
+ }
+
+ RefPtr<PreloaderBase> fetchPreload = FindPreload(uri);
+ if (fetchPreload) {
+ fetchPreload->RemoveSelf(mDocument);
+ fetchPreload->NotifyUsage(PreloaderBase::LoadBackground::Keep);
+
+ rv = fetchPreload->AsyncConsume(this);
+ if (NS_SUCCEEDED(rv)) {
+ mFromPreload = true;
+
+ mChannel = fetchPreload->Channel();
+ MOZ_ASSERT(mChannel);
+ mChannel->SetNotificationCallbacks(this);
+
+ // Copied from AsyncOnChannelRedirect.
+ for (const auto& redirect : fetchPreload->Redirects()) {
+ if (redirect.Flags() & nsIChannelEventSink::REDIRECT_INTERNAL) {
+ mRequest->SetURLForInternalRedirect(redirect.Flags(), redirect.Spec(),
+ redirect.Fragment());
+ } else {
+ mRequest->AddURL(redirect.Spec(), redirect.Fragment());
+ }
+ }
+
+ return NS_OK;
+ }
+
+ // The preload failed to be consumed. Behave like there were no preload.
+ fetchPreload = nullptr;
+ }
+
+ // Step 2 deals with letting ServiceWorkers intercept requests. This is
+ // handled by Necko after the channel is opened.
+ // FIXME(nsm): Bug 1119026: The channel's skip service worker flag should be
+ // set based on the Request's flag.
+
+ // Step 3.1 "If the CORS preflight flag is set and one of these conditions is
+ // true..." is handled by the CORS proxy.
+ //
+ // Step 3.2 "Set request's skip service worker flag." This isn't required
+ // since Necko will fall back to the network if the ServiceWorker does not
+ // respond with a valid Response.
+ //
+ // NS_StartCORSPreflight() will automatically kick off the original request
+ // if it succeeds, so we need to have everything setup for the original
+ // request too.
+
+ // Step 3.3 "Let credentials flag be set if one of
+ // - request's credentials mode is "include"
+ // - request's credentials mode is "same-origin" and either the CORS flag
+ // is unset or response tainting is "opaque"
+ // is true, and unset otherwise."
+
+ // Set skip serviceworker flag.
+ // While the spec also gates on the client being a ServiceWorker, we can't
+ // infer that here. Instead we rely on callers to set the flag correctly.
+ const nsLoadFlags bypassFlag = mRequest->SkipServiceWorker()
+ ? nsIChannel::LOAD_BYPASS_SERVICE_WORKER
+ : 0;
+
+ nsSecurityFlags secFlags = 0;
+ if (mRequest->Mode() == RequestMode::Cors) {
+ secFlags |= nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
+ } else if (mRequest->Mode() == RequestMode::Same_origin ||
+ mRequest->Mode() == RequestMode::Navigate) {
+ secFlags |= nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT;
+ } else if (mRequest->Mode() == RequestMode::No_cors) {
+ secFlags |= nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected request mode!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mRequest->GetRedirectMode() != RequestRedirect::Follow) {
+ secFlags |= nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS;
+ }
+
+ // This is handles the use credentials flag in "HTTP
+ // network or cache fetch" in the spec and decides whether to transmit
+ // cookies and other identifying information.
+ if (mRequest->GetCredentialsMode() == RequestCredentials::Include) {
+ secFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ } else if (mRequest->GetCredentialsMode() == RequestCredentials::Omit) {
+ secFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
+ } else if (mRequest->GetCredentialsMode() ==
+ RequestCredentials::Same_origin) {
+ secFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected credentials mode!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // From here on we create a channel and set its properties with the
+ // information from the InternalRequest. This is an implementation detail.
+ MOZ_ASSERT(mLoadGroup);
+ nsCOMPtr<nsIChannel> chan;
+
+ nsLoadFlags loadFlags = nsIRequest::LOAD_BACKGROUND | bypassFlag;
+ if (mDocument) {
+ MOZ_ASSERT(mDocument->NodePrincipal() == mPrincipal);
+ MOZ_ASSERT(mDocument->CookieJarSettings() == mCookieJarSettings);
+ rv = NS_NewChannel(getter_AddRefs(chan), uri, mDocument, secFlags,
+ mRequest->ContentPolicyType(),
+ nullptr, /* aPerformanceStorage */
+ mLoadGroup, nullptr, /* aCallbacks */
+ loadFlags, ios);
+ } else if (mClientInfo.isSome()) {
+ rv = NS_NewChannel(getter_AddRefs(chan), uri, mPrincipal, mClientInfo.ref(),
+ mController, secFlags, mRequest->ContentPolicyType(),
+ mCookieJarSettings, mPerformanceStorage, mLoadGroup,
+ nullptr, /* aCallbacks */
+ loadFlags, ios);
+ } else {
+ rv =
+ NS_NewChannel(getter_AddRefs(chan), uri, mPrincipal, secFlags,
+ mRequest->ContentPolicyType(), mCookieJarSettings,
+ mPerformanceStorage, mLoadGroup, nullptr, /* aCallbacks */
+ loadFlags, ios);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mCSPEventListener) {
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ rv = loadInfo->SetCspEventListener(mCSPEventListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ {
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ rv = loadInfo->SetLoadingEmbedderPolicy(mRequest->GetEmbedderPolicy());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Insert ourselves into the notification callbacks chain so we can set
+ // headers on redirects.
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
+ chan->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks));
+ MOZ_ASSERT(!notificationCallbacks);
+ }
+#endif
+ chan->SetNotificationCallbacks(this);
+
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(chan));
+ // Mark channel as urgent-start if the Fetch is triggered by user input
+ // events.
+ if (cos && UserActivation::IsHandlingUserInput()) {
+ cos->AddClassFlags(nsIClassOfService::UrgentStart);
+ }
+
+ // Step 3.5 begins "HTTP network or cache fetch".
+ // HTTP network or cache fetch
+ // ---------------------------
+ // Step 1 "Let HTTPRequest..." The channel is the HTTPRequest.
+ nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan);
+ if (httpChan) {
+ // Copy the method.
+ nsAutoCString method;
+ mRequest->GetMethod(method);
+ rv = httpChan->SetRequestMethod(method);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set the same headers.
+ SetRequestHeaders(httpChan, false);
+
+ // Step 5 of https://fetch.spec.whatwg.org/#main-fetch
+ // If request's referrer policy is the empty string and request's client is
+ // non-null, then set request's referrer policy to request's client's
+ // associated referrer policy.
+ // Basically, "client" is not in our implementation, we use
+ // EnvironmentReferrerPolicy of the worker or document context
+ ReferrerPolicy referrerPolicy = mRequest->GetEnvironmentReferrerPolicy();
+ if (mRequest->ReferrerPolicy_() == ReferrerPolicy::_empty) {
+ mRequest->SetReferrerPolicy(referrerPolicy);
+ }
+ // Step 6 of https://fetch.spec.whatwg.org/#main-fetch
+ // If request’s referrer policy is the empty string,
+ // then set request’s referrer policy to the user-set default policy.
+ if (mRequest->ReferrerPolicy_() == ReferrerPolicy::_empty) {
+ nsCOMPtr<nsILoadInfo> loadInfo = httpChan->LoadInfo();
+ bool isPrivate = loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ referrerPolicy =
+ ReferrerInfo::GetDefaultReferrerPolicy(httpChan, uri, isPrivate);
+ mRequest->SetReferrerPolicy(referrerPolicy);
+ }
+
+ rv = FetchUtil::SetRequestReferrer(mPrincipal, mDocument, httpChan,
+ *mRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Bug 1120722 - Authorization will be handled later.
+ // Auth may require prompting, we don't support it yet.
+ // The next patch in this same bug prevents this from aborting the request.
+ // Credentials checks for CORS are handled by nsCORSListenerProxy,
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(httpChan);
+
+ // Conversion between enumerations is safe due to static asserts in
+ // dom/workers/ServiceWorkerManager.cpp
+ rv = internalChan->SetCorsMode(static_cast<uint32_t>(mRequest->Mode()));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = internalChan->SetRedirectMode(
+ static_cast<uint32_t>(mRequest->GetRedirectMode()));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mRequest->MaybeSkipCacheIfPerformingRevalidation();
+ rv = internalChan->SetFetchCacheMode(
+ static_cast<uint32_t>(mRequest->GetCacheMode()));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = internalChan->SetIntegrityMetadata(mRequest->GetIntegrity());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Set the initiator type
+ nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChan));
+ if (timedChannel) {
+ timedChannel->SetInitiatorType(u"fetch"_ns);
+ }
+ }
+
+ // Step 5. Proxy authentication will be handled by Necko.
+
+ // Continue setting up 'HTTPRequest'. Content-Type and body data.
+ nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(chan);
+ if (uploadChan) {
+ nsAutoCString contentType;
+ ErrorResult result;
+ mRequest->Headers()->GetFirst("content-type"_ns, contentType, result);
+ // We don't actually expect "result" to have failed here: that only happens
+ // for invalid header names. But if for some reason it did, just propagate
+ // it out.
+ if (result.Failed()) {
+ return result.StealNSResult();
+ }
+
+ // Now contentType is the header that was set in mRequest->Headers(), or a
+ // void string if no header was set.
+#ifdef DEBUG
+ bool hasContentTypeHeader =
+ mRequest->Headers()->Has("content-type"_ns, result);
+ MOZ_ASSERT(!result.Failed());
+ MOZ_ASSERT_IF(!hasContentTypeHeader, contentType.IsVoid());
+#endif // DEBUG
+
+ int64_t bodyLength;
+ nsCOMPtr<nsIInputStream> bodyStream;
+ mRequest->GetBody(getter_AddRefs(bodyStream), &bodyLength);
+ if (bodyStream) {
+ nsAutoCString method;
+ mRequest->GetMethod(method);
+ rv = uploadChan->ExplicitSetUploadStream(bodyStream, contentType,
+ bodyLength, method,
+ false /* aStreamHasHeaders */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // If preflight is required, start a "CORS preflight fetch"
+ // https://fetch.spec.whatwg.org/#cors-preflight-fetch-0. All the
+ // implementation is handled by the http channel calling into
+ // nsCORSListenerProxy. We just inform it which unsafe headers are included
+ // in the request.
+ if (mRequest->Mode() == RequestMode::Cors) {
+ AutoTArray<nsCString, 5> unsafeHeaders;
+ mRequest->Headers()->GetUnsafeHeaders(unsafeHeaders);
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ loadInfo->SetCorsPreflightInfo(unsafeHeaders, false);
+ }
+
+ if (mIsTrackingFetch && StaticPrefs::network_http_tailing_enabled() && cos) {
+ cos->AddClassFlags(nsIClassOfService::Throttleable |
+ nsIClassOfService::Tail);
+ }
+
+ if (mIsTrackingFetch &&
+ StaticPrefs::privacy_trackingprotection_lower_network_priority()) {
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(chan);
+ if (p) {
+ p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+ }
+ }
+
+ NotifyNetworkMonitorAlternateStack(chan, std::move(mOriginStack));
+
+ // if the preferred alternative data type in InternalRequest is not empty, set
+ // the data type on the created channel and also create a
+ // AlternativeDataStreamListener to be the stream listener of the channel.
+ if (!aPreferredAlternativeDataType.IsEmpty()) {
+ nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(chan);
+ if (cic) {
+ cic->PreferAlternativeDataType(aPreferredAlternativeDataType, ""_ns,
+ true);
+ MOZ_ASSERT(!mAltDataListener);
+ mAltDataListener = new AlternativeDataStreamListener(
+ this, chan, aPreferredAlternativeDataType);
+ rv = chan->AsyncOpen(mAltDataListener);
+ } else {
+ rv = chan->AsyncOpen(this);
+ }
+ } else {
+ // Integrity check cannot be done on alt-data yet.
+ if (mRequest->GetIntegrity().IsEmpty()) {
+ nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(chan);
+ if (cic) {
+ cic->PreferAlternativeDataType(nsLiteralCString(WASM_ALT_DATA_TYPE_V1),
+ nsLiteralCString(WASM_CONTENT_TYPE),
+ false);
+ }
+ }
+
+ rv = chan->AsyncOpen(this);
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Step 4 onwards of "HTTP Fetch" is handled internally by Necko.
+
+ mChannel = chan;
+ return NS_OK;
+}
+already_AddRefed<InternalResponse> FetchDriver::BeginAndGetFilteredResponse(
+ InternalResponse* aResponse, bool aFoundOpaqueRedirect) {
+ MOZ_ASSERT(aResponse);
+ AutoTArray<nsCString, 4> reqURLList;
+ mRequest->GetURLListWithoutFragment(reqURLList);
+ MOZ_ASSERT(!reqURLList.IsEmpty());
+ aResponse->SetURLList(reqURLList);
+ RefPtr<InternalResponse> filteredResponse;
+ if (aFoundOpaqueRedirect) {
+ filteredResponse = aResponse->OpaqueRedirectResponse();
+ } else {
+ switch (mRequest->GetResponseTainting()) {
+ case LoadTainting::Basic:
+ filteredResponse = aResponse->BasicResponse();
+ break;
+ case LoadTainting::CORS:
+ filteredResponse = aResponse->CORSResponse();
+ break;
+ case LoadTainting::Opaque: {
+ filteredResponse = aResponse->OpaqueResponse();
+ nsresult rv = filteredResponse->GeneratePaddingInfo();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ break;
+ }
+ default:
+ MOZ_CRASH("Unexpected case");
+ }
+ }
+
+ MOZ_ASSERT(filteredResponse);
+ MOZ_ASSERT(mObserver);
+ if (!ShouldCheckSRI(*mRequest, *filteredResponse)) {
+ // Need to keep mObserver alive.
+ RefPtr<FetchDriverObserver> observer = mObserver;
+ observer->OnResponseAvailable(filteredResponse);
+#ifdef DEBUG
+ mResponseAvailableCalled = true;
+#endif
+ }
+
+ return filteredResponse.forget();
+}
+
+void FetchDriver::FailWithNetworkError(nsresult rv) {
+ AssertIsOnMainThread();
+ RefPtr<InternalResponse> error = InternalResponse::NetworkError(rv);
+ if (mObserver) {
+ // Need to keep mObserver alive.
+ RefPtr<FetchDriverObserver> observer = mObserver;
+ observer->OnResponseAvailable(error);
+#ifdef DEBUG
+ mResponseAvailableCalled = true;
+#endif
+ }
+
+ // mObserver could be null after OnResponseAvailable().
+ if (mObserver) {
+ mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking);
+ mObserver = nullptr;
+ }
+
+ mChannel = nullptr;
+}
+
+NS_IMETHODIMP
+FetchDriver::OnStartRequest(nsIRequest* aRequest) {
+ AssertIsOnMainThread();
+
+ // Note, this can be called multiple times if we are doing an opaqueredirect.
+ // In that case we will get a simulated OnStartRequest() and then the real
+ // channel will call in with an errored OnStartRequest().
+
+ if (mFromPreload && mAborted) {
+ aRequest->Cancel(NS_BINDING_ABORTED);
+ return NS_BINDING_ABORTED;
+ }
+
+ if (!mChannel) {
+ MOZ_ASSERT(!mObserver);
+ return NS_BINDING_ABORTED;
+ }
+
+ nsresult rv;
+ aRequest->GetStatus(&rv);
+ if (NS_FAILED(rv)) {
+ FailWithNetworkError(rv);
+ return rv;
+ }
+
+ // We should only get to the following code once.
+ MOZ_ASSERT(!mPipeOutputStream);
+
+ if (!mObserver) {
+ MOZ_ASSERT(false, "We should have mObserver here.");
+ FailWithNetworkError(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mNeedToObserveOnDataAvailable = mObserver->NeedOnDataAvailable();
+
+ RefPtr<InternalResponse> response;
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+
+ // On a successful redirect we perform the following substeps of HTTP Fetch,
+ // step 5, "redirect status", step 11.
+
+ bool foundOpaqueRedirect = false;
+
+ nsAutoCString contentType;
+ channel->GetContentType(contentType);
+
+ int64_t contentLength = InternalResponse::UNKNOWN_BODY_SIZE;
+ rv = channel->GetContentLength(&contentLength);
+ MOZ_ASSERT_IF(NS_FAILED(rv),
+ contentLength == InternalResponse::UNKNOWN_BODY_SIZE);
+
+ if (httpChannel) {
+ uint32_t responseStatus;
+ rv = httpChannel->GetResponseStatus(&responseStatus);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (mozilla::net::nsHttpChannel::IsRedirectStatus(responseStatus)) {
+ if (mRequest->GetRedirectMode() == RequestRedirect::Error) {
+ FailWithNetworkError(NS_BINDING_ABORTED);
+ return NS_BINDING_FAILED;
+ }
+ if (mRequest->GetRedirectMode() == RequestRedirect::Manual) {
+ foundOpaqueRedirect = true;
+ }
+ }
+
+ nsAutoCString statusText;
+ rv = httpChannel->GetResponseStatusText(statusText);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ response = new InternalResponse(responseStatus, statusText,
+ mRequest->GetCredentialsMode());
+
+ UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo(
+ new mozilla::ipc::PrincipalInfo());
+ nsresult rv = PrincipalToPrincipalInfo(mPrincipal, principalInfo.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ response->SetPrincipalInfo(std::move(principalInfo));
+
+ response->Headers()->FillResponseHeaders(httpChannel);
+
+ // If Content-Encoding or Transfer-Encoding headers are set, then the actual
+ // Content-Length (which refer to the decoded data) is obscured behind the
+ // encodings.
+ ErrorResult result;
+ if (response->Headers()->Has("content-encoding"_ns, result) ||
+ response->Headers()->Has("transfer-encoding"_ns, result)) {
+ // We cannot trust the content-length when content-encoding or
+ // transfer-encoding are set. There are many servers which just
+ // get this wrong.
+ contentLength = InternalResponse::UNKNOWN_BODY_SIZE;
+ }
+ MOZ_ASSERT(!result.Failed());
+ } else {
+ response =
+ new InternalResponse(200, "OK"_ns, mRequest->GetCredentialsMode());
+
+ if (!contentType.IsEmpty()) {
+ nsAutoCString contentCharset;
+ channel->GetContentCharset(contentCharset);
+ if (NS_SUCCEEDED(rv) && !contentCharset.IsEmpty()) {
+ contentType += ";charset="_ns + contentCharset;
+ }
+
+ IgnoredErrorResult result;
+ response->Headers()->Append("Content-Type"_ns, contentType, result);
+ MOZ_ASSERT(!result.Failed());
+ }
+
+ if (contentLength > 0) {
+ nsAutoCString contentLenStr;
+ contentLenStr.AppendInt(contentLength);
+
+ IgnoredErrorResult result;
+ response->Headers()->Append("Content-Length"_ns, contentLenStr, result);
+ MOZ_ASSERT(!result.Failed());
+ }
+ }
+
+ nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(aRequest);
+ if (cic) {
+ if (mAltDataListener) {
+ // Skip the case that mAltDataListener->Status() equals to FALLBACK, that
+ // means the opened channel for alternative data loading is reused for
+ // loading the main data.
+ if (mAltDataListener->Status() !=
+ AlternativeDataStreamListener::FALLBACK) {
+ // Verify the cache ID is the same with from alternative data cache.
+ // If the cache ID is different, droping the alternative data loading,
+ // otherwise setup the response's alternative body and cacheInfoChannel.
+ uint64_t cacheEntryId = 0;
+ if (NS_SUCCEEDED(cic->GetCacheEntryId(&cacheEntryId)) &&
+ cacheEntryId !=
+ mAltDataListener->GetAlternativeDataCacheEntryId()) {
+ mAltDataListener->Cancel();
+ } else {
+ // AlternativeDataStreamListener::OnStartRequest had already been
+ // called, the alternative data input stream and cacheInfo channel
+ // must be created.
+ nsCOMPtr<nsICacheInfoChannel> cacheInfo =
+ mAltDataListener->GetCacheInfoChannel();
+ nsCOMPtr<nsIInputStream> altInputStream =
+ mAltDataListener->GetAlternativeInputStream();
+ MOZ_ASSERT(altInputStream && cacheInfo);
+ response->SetAlternativeBody(altInputStream);
+ nsMainThreadPtrHandle<nsICacheInfoChannel> handle(
+ new nsMainThreadPtrHolder<nsICacheInfoChannel>(
+ "nsICacheInfoChannel", cacheInfo, false));
+ response->SetCacheInfoChannel(handle);
+ }
+ } else if (!mAltDataListener->GetAlternativeDataType().IsEmpty()) {
+ // If the status is FALLBACK and the
+ // mAltDataListener::mAlternativeDataType is not empty, that means the
+ // data need to be saved into cache, setup the response's
+ // nsICacheInfoChannel for caching the data after loading.
+ nsMainThreadPtrHandle<nsICacheInfoChannel> handle(
+ new nsMainThreadPtrHolder<nsICacheInfoChannel>(
+ "nsICacheInfoChannel", cic, false));
+ response->SetCacheInfoChannel(handle);
+ }
+ } else if (!cic->PreferredAlternativeDataTypes().IsEmpty()) {
+ MOZ_ASSERT(cic->PreferredAlternativeDataTypes().Length() == 1);
+ MOZ_ASSERT(cic->PreferredAlternativeDataTypes()[0].type().EqualsLiteral(
+ WASM_ALT_DATA_TYPE_V1));
+ MOZ_ASSERT(
+ cic->PreferredAlternativeDataTypes()[0].contentType().EqualsLiteral(
+ WASM_CONTENT_TYPE));
+
+ if (contentType.EqualsLiteral(WASM_CONTENT_TYPE)) {
+ // We want to attach the CacheInfoChannel to the response object such
+ // that we can track its origin when the Response object is manipulated
+ // by JavaScript code. This is important for WebAssembly, which uses
+ // fetch to query its sources in JavaScript and transfer the Response
+ // object to other function responsible for storing the alternate data
+ // using the CacheInfoChannel.
+ nsMainThreadPtrHandle<nsICacheInfoChannel> handle(
+ new nsMainThreadPtrHolder<nsICacheInfoChannel>(
+ "nsICacheInfoChannel", cic, false));
+ response->SetCacheInfoChannel(handle);
+ }
+ }
+ }
+
+ // We open a pipe so that we can immediately set the pipe's read end as the
+ // response's body. Setting the segment size to UINT32_MAX means that the
+ // pipe has infinite space. The nsIChannel will continue to buffer data in
+ // xpcom events even if we block on a fixed size pipe. It might be possible
+ // to suspend the channel and then resume when there is space available, but
+ // for now use an infinite pipe to avoid blocking.
+ nsCOMPtr<nsIInputStream> pipeInputStream;
+ rv = NS_NewPipe(getter_AddRefs(pipeInputStream),
+ getter_AddRefs(mPipeOutputStream),
+ 0, /* default segment size */
+ UINT32_MAX /* infinite pipe */,
+ true /* non-blocking input, otherwise you deadlock */,
+ false /* blocking output, since the pipe is 'in'finite */);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailWithNetworkError(rv);
+ // Cancel request.
+ return rv;
+ }
+ response->SetBody(pipeInputStream, contentLength);
+
+ // If the request is a file channel, then remember the local path to
+ // that file so we can later create File blobs rather than plain ones.
+ nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aRequest);
+ if (fc) {
+ nsCOMPtr<nsIFile> file;
+ rv = fc->GetFile(getter_AddRefs(file));
+ if (!NS_WARN_IF(NS_FAILED(rv))) {
+ nsAutoString path;
+ file->GetPath(path);
+ response->SetBodyLocalPath(path);
+ }
+ } else {
+ // If the request is a blob URI, then remember that URI so that we
+ // can later just use that blob instance instead of cloning it.
+ nsCString blobURISpec;
+ GetBlobURISpecFromChannel(aRequest, blobURISpec);
+ if (!blobURISpec.IsVoid()) {
+ response->SetBodyBlobURISpec(blobURISpec);
+ }
+ }
+
+ response->InitChannelInfo(channel);
+
+ nsCOMPtr<nsIURI> channelURI;
+ rv = channel->GetURI(getter_AddRefs(channelURI));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailWithNetworkError(rv);
+ // Cancel request.
+ return rv;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ // Propagate any tainting from the channel back to our response here. This
+ // step is not reflected in the spec because the spec is written such that
+ // FetchEvent.respondWith() just passes the already-tainted Response back to
+ // the outer fetch(). In gecko, however, we serialize the Response through
+ // the channel and must regenerate the tainting from the channel in the
+ // interception case.
+ mRequest->MaybeIncreaseResponseTainting(loadInfo->GetTainting());
+
+ // Resolves fetch() promise which may trigger code running in a worker. Make
+ // sure the Response is fully initialized before calling this.
+ mResponse = BeginAndGetFilteredResponse(response, foundOpaqueRedirect);
+ if (NS_WARN_IF(!mResponse)) {
+ // Fail to generate a paddingInfo for opaque response.
+ MOZ_DIAGNOSTIC_ASSERT(mRequest->GetResponseTainting() ==
+ LoadTainting::Opaque &&
+ !foundOpaqueRedirect);
+ FailWithNetworkError(NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // From "Main Fetch" step 19: SRI-part1.
+ if (ShouldCheckSRI(*mRequest, *mResponse) && mSRIMetadata.IsEmpty()) {
+ nsIConsoleReportCollector* reporter = nullptr;
+ if (mObserver) {
+ reporter = mObserver->GetReporter();
+ }
+
+ nsAutoCString sourceUri;
+ if (mDocument && mDocument->GetDocumentURI()) {
+ mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ } else if (!mWorkerScript.IsEmpty()) {
+ sourceUri.Assign(mWorkerScript);
+ }
+ SRICheck::IntegrityMetadata(mRequest->GetIntegrity(), sourceUri, reporter,
+ &mSRIMetadata);
+ mSRIDataVerifier =
+ MakeUnique<SRICheckDataVerifier>(mSRIMetadata, sourceUri, reporter);
+
+ // Do not retarget off main thread when using SRI API.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailWithNetworkError(rv);
+ // Cancel request.
+ return rv;
+ }
+
+ // Try to retarget off main thread.
+ if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) {
+ Unused << NS_WARN_IF(NS_FAILED(rr->RetargetDeliveryTo(sts)));
+ }
+ return NS_OK;
+}
+
+namespace {
+
+// Runnable to call the observer OnDataAvailable on the main-thread.
+class DataAvailableRunnable final : public Runnable {
+ RefPtr<FetchDriverObserver> mObserver;
+
+ public:
+ explicit DataAvailableRunnable(FetchDriverObserver* aObserver)
+ : Runnable("dom::DataAvailableRunnable"), mObserver(aObserver) {
+ MOZ_ASSERT(aObserver);
+ }
+
+ NS_IMETHOD
+ Run() override {
+ mObserver->OnDataAvailable();
+ mObserver = nullptr;
+ return NS_OK;
+ }
+};
+
+struct SRIVerifierAndOutputHolder {
+ SRIVerifierAndOutputHolder(SRICheckDataVerifier* aVerifier,
+ nsIOutputStream* aOutputStream)
+ : mVerifier(aVerifier), mOutputStream(aOutputStream) {}
+
+ SRICheckDataVerifier* mVerifier;
+ nsIOutputStream* mOutputStream;
+
+ private:
+ SRIVerifierAndOutputHolder() = delete;
+};
+
+// Just like NS_CopySegmentToStream, but also sends the data into an
+// SRICheckDataVerifier.
+nsresult CopySegmentToStreamAndSRI(nsIInputStream* aInStr, void* aClosure,
+ const char* aBuffer, uint32_t aOffset,
+ uint32_t aCount, uint32_t* aCountWritten) {
+ auto holder = static_cast<SRIVerifierAndOutputHolder*>(aClosure);
+ MOZ_DIAGNOSTIC_ASSERT(holder && holder->mVerifier && holder->mOutputStream,
+ "Bogus holder");
+ nsresult rv = holder->mVerifier->Update(
+ aCount, reinterpret_cast<const uint8_t*>(aBuffer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The rest is just like NS_CopySegmentToStream.
+ *aCountWritten = 0;
+ while (aCount) {
+ uint32_t n = 0;
+ rv = holder->mOutputStream->Write(aBuffer, aCount, &n);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aBuffer += n;
+ aCount -= n;
+ *aCountWritten += n;
+ }
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+FetchDriver::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // NB: This can be called on any thread! But we're guaranteed that it is
+ // called between OnStartRequest and OnStopRequest, so we don't need to worry
+ // about races.
+
+ if (mNeedToObserveOnDataAvailable) {
+ mNeedToObserveOnDataAvailable = false;
+ if (mObserver) {
+ // Need to keep mObserver alive.
+ RefPtr<FetchDriverObserver> observer = mObserver;
+ if (NS_IsMainThread()) {
+ observer->OnDataAvailable();
+ } else {
+ RefPtr<Runnable> runnable = new DataAvailableRunnable(observer);
+ nsresult rv = mMainThreadEventTarget->Dispatch(runnable.forget(),
+ NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+ }
+
+ // Needs to be initialized to 0 because in some cases nsStringInputStream may
+ // not write to aRead.
+ uint32_t aRead = 0;
+ MOZ_ASSERT(mResponse);
+ MOZ_ASSERT(mPipeOutputStream);
+
+ // From "Main Fetch" step 19: SRI-part2.
+ // Note: Avoid checking the hidden opaque body.
+ nsresult rv;
+ if (mResponse->Type() != ResponseType::Opaque &&
+ ShouldCheckSRI(*mRequest, *mResponse)) {
+ MOZ_ASSERT(mSRIDataVerifier);
+
+ SRIVerifierAndOutputHolder holder(mSRIDataVerifier.get(),
+ mPipeOutputStream);
+ rv = aInputStream->ReadSegments(CopySegmentToStreamAndSRI, &holder, aCount,
+ &aRead);
+ } else {
+ rv = aInputStream->ReadSegments(NS_CopySegmentToStream, mPipeOutputStream,
+ aCount, &aRead);
+ }
+
+ // If no data was read, it's possible the output stream is closed but the
+ // ReadSegments call followed its contract of returning NS_OK despite write
+ // errors. Unfortunately, nsIOutputStream has an ill-conceived contract when
+ // taken together with ReadSegments' contract, because the pipe will just
+ // NS_OK if we try and invoke its Write* functions ourselves with a 0 count.
+ // So we must just assume the pipe is broken.
+ if (aRead == 0 && aCount != 0) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+FetchDriver::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ AssertIsOnMainThread();
+
+ MOZ_DIAGNOSTIC_ASSERT(!mOnStopRequestCalled);
+ mOnStopRequestCalled = true;
+
+ // main data loading is going to finish, breaking the reference cycle.
+ RefPtr<AlternativeDataStreamListener> altDataListener =
+ std::move(mAltDataListener);
+
+ // We need to check mObserver, which is nulled by FailWithNetworkError(),
+ // because in the case of "error" redirect mode, aStatusCode may be NS_OK but
+ // mResponse will definitely be null so we must not take the else branch.
+ if (NS_FAILED(aStatusCode) || !mObserver) {
+ nsCOMPtr<nsIAsyncOutputStream> outputStream =
+ do_QueryInterface(mPipeOutputStream);
+ if (outputStream) {
+ outputStream->CloseWithStatus(NS_FAILED(aStatusCode) ? aStatusCode
+ : NS_BINDING_FAILED);
+ }
+ if (altDataListener) {
+ altDataListener->Cancel();
+ }
+
+ // We proceed as usual here, since we've already created a successful
+ // response from OnStartRequest.
+ } else {
+ MOZ_ASSERT(mResponse);
+ MOZ_ASSERT(!mResponse->IsError());
+
+ // From "Main Fetch" step 19: SRI-part3.
+ if (ShouldCheckSRI(*mRequest, *mResponse)) {
+ MOZ_ASSERT(mSRIDataVerifier);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+
+ nsIConsoleReportCollector* reporter = nullptr;
+ if (mObserver) {
+ reporter = mObserver->GetReporter();
+ }
+
+ nsAutoCString sourceUri;
+ if (mDocument && mDocument->GetDocumentURI()) {
+ mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ } else if (!mWorkerScript.IsEmpty()) {
+ sourceUri.Assign(mWorkerScript);
+ }
+ nsresult rv =
+ mSRIDataVerifier->Verify(mSRIMetadata, channel, sourceUri, reporter);
+ if (NS_FAILED(rv)) {
+ if (altDataListener) {
+ altDataListener->Cancel();
+ }
+ FailWithNetworkError(rv);
+ // Cancel request.
+ return rv;
+ }
+ }
+
+ if (mPipeOutputStream) {
+ mPipeOutputStream->Close();
+ }
+ }
+
+ FinishOnStopRequest(altDataListener);
+ return NS_OK;
+}
+
+void FetchDriver::FinishOnStopRequest(
+ AlternativeDataStreamListener* aAltDataListener) {
+ AssertIsOnMainThread();
+ // OnStopRequest is not called from channel, that means the main data loading
+ // does not finish yet. Reaching here since alternative data loading finishes.
+ if (!mOnStopRequestCalled) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!mAltDataListener);
+ // Wait for alternative data loading finish if we needed it.
+ if (aAltDataListener &&
+ aAltDataListener->Status() == AlternativeDataStreamListener::LOADING) {
+ // For LOADING case, channel holds the reference of altDataListener, no need
+ // to restore it to mAltDataListener.
+ return;
+ }
+
+ if (mObserver) {
+ // From "Main Fetch" step 19.1, 19.2: Process response.
+ if (ShouldCheckSRI(*mRequest, *mResponse)) {
+ MOZ_ASSERT(mResponse);
+ // Need to keep mObserver alive.
+ RefPtr<FetchDriverObserver> observer = mObserver;
+ observer->OnResponseAvailable(mResponse);
+#ifdef DEBUG
+ mResponseAvailableCalled = true;
+#endif
+ }
+ }
+
+ if (mObserver) {
+ mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking);
+ mObserver = nullptr;
+ }
+
+ mChannel = nullptr;
+}
+
+NS_IMETHODIMP
+FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel, uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback) {
+ nsCOMPtr<nsIHttpChannel> oldHttpChannel = do_QueryInterface(aOldChannel);
+ nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(aNewChannel);
+ if (oldHttpChannel && newHttpChannel) {
+ nsAutoCString method;
+ mRequest->GetMethod(method);
+
+ // Fetch 4.4.11
+ bool rewriteToGET = false;
+ Unused << oldHttpChannel->ShouldStripRequestBodyHeader(method,
+ &rewriteToGET);
+
+ SetRequestHeaders(newHttpChannel, rewriteToGET);
+ }
+
+ // "HTTP-redirect fetch": step 14 "Append locationURL to request's URL list."
+ // However, ignore internal redirects here. We don't want to flip
+ // Response.redirected to true if an internal redirect occurs. These
+ // should be transparent to script.
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(aNewChannel->GetURI(getter_AddRefs(uri)));
+
+ nsCOMPtr<nsIURI> uriClone;
+ nsresult rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriClone));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ nsCString spec;
+ rv = uriClone->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ nsCString fragment;
+ rv = uri->GetRef(fragment);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
+ mRequest->AddURL(spec, fragment);
+ } else {
+ // Overwrite the URL only when the request is redirected by a service
+ // worker.
+ mRequest->SetURLForInternalRedirect(aFlags, spec, fragment);
+ }
+
+ // In redirect, httpChannel already took referrer-policy into account, so
+ // updates request’s associated referrer policy from channel.
+ UpdateReferrerInfoFromNewChannel(aNewChannel);
+
+ aCallback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FetchDriver::CheckListenerChain() { return NS_OK; }
+
+NS_IMETHODIMP
+FetchDriver::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+ if (aIID.Equals(NS_GET_IID(nsIStreamListener))) {
+ *aResult = static_cast<nsIStreamListener*>(this);
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+ if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) {
+ *aResult = static_cast<nsIRequestObserver*>(this);
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+void FetchDriver::SetDocument(Document* aDocument) {
+ // Cannot set document after Fetch() has been called.
+ MOZ_ASSERT(!mFetchCalled);
+ mDocument = aDocument;
+}
+
+void FetchDriver::SetCSPEventListener(nsICSPEventListener* aCSPEventListener) {
+ MOZ_ASSERT(!mFetchCalled);
+ mCSPEventListener = aCSPEventListener;
+}
+
+void FetchDriver::SetClientInfo(const ClientInfo& aClientInfo) {
+ MOZ_ASSERT(!mFetchCalled);
+ mClientInfo.emplace(aClientInfo);
+}
+
+void FetchDriver::SetController(
+ const Maybe<ServiceWorkerDescriptor>& aController) {
+ MOZ_ASSERT(!mFetchCalled);
+ mController = aController;
+}
+
+void FetchDriver::SetRequestHeaders(nsIHttpChannel* aChannel,
+ bool aStripRequestBodyHeader) const {
+ MOZ_ASSERT(aChannel);
+
+ // nsIHttpChannel has a set of pre-configured headers (Accept,
+ // Accept-Languages, ...) and we don't want to merge the Request's headers
+ // with them. This array is used to know if the current header has been aleady
+ // set, if yes, we ask necko to merge it with the previous one, otherwise, we
+ // don't want the merge.
+ nsTArray<nsCString> headersSet;
+
+ AutoTArray<InternalHeaders::Entry, 5> headers;
+ mRequest->Headers()->GetEntries(headers);
+ for (uint32_t i = 0; i < headers.Length(); ++i) {
+ if (aStripRequestBodyHeader &&
+ (headers[i].mName.LowerCaseEqualsASCII("content-type") ||
+ headers[i].mName.LowerCaseEqualsASCII("content-encoding") ||
+ headers[i].mName.LowerCaseEqualsASCII("content-language") ||
+ headers[i].mName.LowerCaseEqualsASCII("content-location"))) {
+ continue;
+ }
+
+ bool alreadySet = headersSet.Contains(headers[i].mName);
+ if (!alreadySet) {
+ headersSet.AppendElement(headers[i].mName);
+ }
+
+ if (headers[i].mValue.IsEmpty()) {
+ DebugOnly<nsresult> rv =
+ aChannel->SetEmptyRequestHeader(headers[i].mName);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ } else {
+ DebugOnly<nsresult> rv = aChannel->SetRequestHeader(
+ headers[i].mName, headers[i].mValue, alreadySet /* merge */);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ nsAutoCString method;
+ mRequest->GetMethod(method);
+ if (!method.EqualsLiteral("GET") && !method.EqualsLiteral("HEAD")) {
+ nsAutoString origin;
+ if (NS_SUCCEEDED(nsContentUtils::GetUTFOrigin(mPrincipal, origin))) {
+ DebugOnly<nsresult> rv = aChannel->SetRequestHeader(
+ nsDependentCString(net::nsHttp::Origin),
+ NS_ConvertUTF16toUTF8(origin), false /* merge */);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+}
+
+void FetchDriver::RunAbortAlgorithm() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ if (mObserver) {
+#ifdef DEBUG
+ mResponseAvailableCalled = true;
+#endif
+ mObserver->OnResponseEnd(FetchDriverObserver::eAborted);
+ mObserver = nullptr;
+ }
+
+ if (mChannel) {
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ mChannel = nullptr;
+ }
+
+ mAborted = true;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/FetchDriver.h b/dom/fetch/FetchDriver.h
new file mode 100644
index 0000000000..2331b1e4ea
--- /dev/null
+++ b/dom/fetch/FetchDriver.h
@@ -0,0 +1,210 @@
+/* -*- 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_FetchDriver_h
+#define mozilla_dom_FetchDriver_h
+
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/dom/AbortSignal.h"
+#include "mozilla/dom/SafeRefPtr.h"
+#include "mozilla/dom/SerializedStackHolder.h"
+#include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+#include "mozilla/DebugOnly.h"
+
+class nsIConsoleReportCollector;
+class nsICookieJarSettings;
+class nsICSPEventListener;
+class nsIEventTarget;
+class nsIOutputStream;
+class nsILoadGroup;
+class nsIPrincipal;
+
+namespace mozilla {
+class PreloaderBase;
+
+namespace dom {
+
+class Document;
+class InternalRequest;
+class InternalResponse;
+class PerformanceStorage;
+
+/**
+ * Provides callbacks to be called when response is available or on error.
+ * Implemenations usually resolve or reject the promise returned from fetch().
+ * The callbacks can be called synchronously or asynchronously from
+ * FetchDriver::Fetch.
+ */
+class FetchDriverObserver {
+ public:
+ FetchDriverObserver()
+ : mReporter(new ConsoleReportCollector()), mGotResponseAvailable(false) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FetchDriverObserver);
+ void OnResponseAvailable(InternalResponse* aResponse) {
+ MOZ_ASSERT(!mGotResponseAvailable);
+ mGotResponseAvailable = true;
+ OnResponseAvailableInternal(aResponse);
+ }
+
+ enum EndReason {
+ eAborted,
+ eByNetworking,
+ };
+
+ virtual void OnResponseEnd(EndReason aReason){};
+
+ nsIConsoleReportCollector* GetReporter() const { return mReporter; }
+
+ virtual void FlushConsoleReport() = 0;
+
+ // Called in OnStartRequest() to determine if the OnDataAvailable() method
+ // needs to be called. Invoking that method may generate additional main
+ // thread runnables.
+ virtual bool NeedOnDataAvailable() = 0;
+
+ // Called once when the first byte of data is received iff
+ // NeedOnDataAvailable() returned true when called in OnStartRequest().
+ virtual void OnDataAvailable() = 0;
+
+ protected:
+ virtual ~FetchDriverObserver() = default;
+
+ virtual void OnResponseAvailableInternal(InternalResponse* aResponse) = 0;
+
+ nsCOMPtr<nsIConsoleReportCollector> mReporter;
+
+ private:
+ bool mGotResponseAvailable;
+};
+
+class AlternativeDataStreamListener;
+
+class FetchDriver final : public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIInterfaceRequestor,
+ public nsIThreadRetargetableStreamListener,
+ public AbortFollower {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ FetchDriver(SafeRefPtr<InternalRequest> aRequest, nsIPrincipal* aPrincipal,
+ nsILoadGroup* aLoadGroup, nsIEventTarget* aMainThreadEventTarget,
+ nsICookieJarSettings* aCookieJarSettings,
+ PerformanceStorage* aPerformanceStorage, bool aIsTrackingFetch);
+
+ nsresult Fetch(AbortSignalImpl* aSignalImpl, FetchDriverObserver* aObserver);
+
+ void SetDocument(Document* aDocument);
+
+ void SetCSPEventListener(nsICSPEventListener* aCSPEventListener);
+
+ void SetClientInfo(const ClientInfo& aClientInfo);
+
+ void SetController(const Maybe<ServiceWorkerDescriptor>& aController);
+
+ void SetWorkerScript(const nsACString& aWorkerScript) {
+ MOZ_ASSERT(!aWorkerScript.IsEmpty());
+ mWorkerScript = aWorkerScript;
+ }
+
+ void SetOriginStack(UniquePtr<SerializedStackHolder>&& aOriginStack) {
+ mOriginStack = std::move(aOriginStack);
+ }
+
+ // AbortFollower
+ void RunAbortAlgorithm() override;
+
+ private:
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ SafeRefPtr<InternalRequest> mRequest;
+ RefPtr<InternalResponse> mResponse;
+ nsCOMPtr<nsIOutputStream> mPipeOutputStream;
+ RefPtr<FetchDriverObserver> mObserver;
+ RefPtr<Document> mDocument;
+ nsCOMPtr<nsICSPEventListener> mCSPEventListener;
+ Maybe<ClientInfo> mClientInfo;
+ Maybe<ServiceWorkerDescriptor> mController;
+ nsCOMPtr<nsIChannel> mChannel;
+ UniquePtr<SRICheckDataVerifier> mSRIDataVerifier;
+ nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+
+ // This is set only when Fetch is used in workers.
+ RefPtr<PerformanceStorage> mPerformanceStorage;
+
+ SRIMetadata mSRIMetadata;
+ nsCString mWorkerScript;
+ UniquePtr<SerializedStackHolder> mOriginStack;
+
+ // This is written once in OnStartRequest on the main thread and then
+ // written/read in OnDataAvailable() on any thread. Necko guarantees
+ // that these do not overlap.
+ bool mNeedToObserveOnDataAvailable;
+
+ bool mIsTrackingFetch;
+
+ RefPtr<AlternativeDataStreamListener> mAltDataListener;
+ bool mOnStopRequestCalled;
+
+ // This flag is true when this fetch has found a matching preload and is being
+ // satisfied by a its response.
+ bool mFromPreload = false;
+ // This flag is set in call to Abort() and spans the possible window this
+ // fetch doesn't have mChannel (to be cancelled) between reuse of the matching
+ // preload, that has already finished and dropped reference to its channel,
+ // and OnStartRequest notification. It let's us cancel the load when we get
+ // the channel in OnStartRequest.
+ bool mAborted = false;
+
+#ifdef DEBUG
+ bool mResponseAvailableCalled;
+ bool mFetchCalled;
+#endif
+
+ friend class AlternativeDataStreamListener;
+
+ FetchDriver() = delete;
+ FetchDriver(const FetchDriver&) = delete;
+ FetchDriver& operator=(const FetchDriver&) = delete;
+ ~FetchDriver();
+
+ already_AddRefed<PreloaderBase> FindPreload(nsIURI* aURI);
+
+ void UpdateReferrerInfoFromNewChannel(nsIChannel* aChannel);
+
+ nsresult HttpFetch(const nsACString& aPreferredAlternativeDataType = ""_ns);
+ // Returns the filtered response sent to the observer.
+ already_AddRefed<InternalResponse> BeginAndGetFilteredResponse(
+ InternalResponse* aResponse, bool aFoundOpaqueRedirect);
+ // Utility since not all cases need to do any post processing of the filtered
+ // response.
+ void FailWithNetworkError(nsresult rv);
+
+ void SetRequestHeaders(nsIHttpChannel* aChannel,
+ bool aStripRequestBodyHeader) const;
+
+ void FinishOnStopRequest(AlternativeDataStreamListener* aAltDataListener);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FetchDriver_h
diff --git a/dom/fetch/FetchIPCTypes.h b/dom/fetch/FetchIPCTypes.h
new file mode 100644
index 0000000000..b8ae402bfa
--- /dev/null
+++ b/dom/fetch/FetchIPCTypes.h
@@ -0,0 +1,56 @@
+/* -*- 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_fetch_IPCUtils_h
+#define mozilla_dom_fetch_IPCUtils_h
+
+#include "ipc/EnumSerializer.h"
+
+#include "mozilla/dom/HeadersBinding.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/ResponseBinding.h"
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::dom::HeadersGuardEnum>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::HeadersGuardEnum, mozilla::dom::HeadersGuardEnum::None,
+ mozilla::dom::HeadersGuardEnum::EndGuard_> {};
+template <>
+struct ParamTraits<mozilla::dom::ReferrerPolicy>
+ : public ContiguousEnumSerializer<mozilla::dom::ReferrerPolicy,
+ mozilla::dom::ReferrerPolicy::_empty,
+ mozilla::dom::ReferrerPolicy::EndGuard_> {
+};
+template <>
+struct ParamTraits<mozilla::dom::RequestMode>
+ : public ContiguousEnumSerializer<mozilla::dom::RequestMode,
+ mozilla::dom::RequestMode::Same_origin,
+ mozilla::dom::RequestMode::EndGuard_> {};
+template <>
+struct ParamTraits<mozilla::dom::RequestCredentials>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::RequestCredentials,
+ mozilla::dom::RequestCredentials::Omit,
+ mozilla::dom::RequestCredentials::EndGuard_> {};
+template <>
+struct ParamTraits<mozilla::dom::RequestCache>
+ : public ContiguousEnumSerializer<mozilla::dom::RequestCache,
+ mozilla::dom::RequestCache::Default,
+ mozilla::dom::RequestCache::EndGuard_> {};
+template <>
+struct ParamTraits<mozilla::dom::RequestRedirect>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::RequestRedirect, mozilla::dom::RequestRedirect::Follow,
+ mozilla::dom::RequestRedirect::EndGuard_> {};
+template <>
+struct ParamTraits<mozilla::dom::ResponseType>
+ : public ContiguousEnumSerializer<mozilla::dom::ResponseType,
+ mozilla::dom::ResponseType::Basic,
+ mozilla::dom::ResponseType::EndGuard_> {};
+} // namespace IPC
+
+#endif // mozilla_dom_fetch_IPCUtils_h
diff --git a/dom/fetch/FetchObserver.cpp b/dom/fetch/FetchObserver.cpp
new file mode 100644
index 0000000000..7bdf6b2db8
--- /dev/null
+++ b/dom/fetch/FetchObserver.cpp
@@ -0,0 +1,81 @@
+/* -*- 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 "FetchObserver.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FetchObserver)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FetchObserver,
+ DOMEventTargetHelper)
+ AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchObserver,
+ DOMEventTargetHelper)
+ AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchObserver)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(FetchObserver, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(FetchObserver, DOMEventTargetHelper)
+
+FetchObserver::FetchObserver(nsIGlobalObject* aGlobal,
+ AbortSignalImpl* aSignalImpl)
+ : DOMEventTargetHelper(aGlobal), mState(FetchState::Requesting) {
+ if (aSignalImpl) {
+ Follow(aSignalImpl);
+ }
+}
+
+JSObject* FetchObserver::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return FetchObserver_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+FetchState FetchObserver::State() const { return mState; }
+
+void FetchObserver::RunAbortAlgorithm() { SetState(FetchState::Aborted); }
+
+void FetchObserver::SetState(FetchState aState) {
+ MOZ_ASSERT(mState < aState);
+
+ if (mState == FetchState::Aborted || mState == FetchState::Errored ||
+ mState == FetchState::Complete) {
+ // We are already in a final state.
+ return;
+ }
+
+ // We cannot pass from Requesting to Complete directly.
+ if (mState == FetchState::Requesting && aState == FetchState::Complete) {
+ SetState(FetchState::Responding);
+ }
+
+ mState = aState;
+
+ if (mState == FetchState::Aborted || mState == FetchState::Errored ||
+ mState == FetchState::Complete) {
+ Unfollow();
+ }
+
+ EventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ // TODO which kind of event should we dispatch here?
+
+ RefPtr<Event> event = Event::Constructor(this, u"statechange"_ns, init);
+ event->SetTrusted(true);
+
+ DispatchEvent(*event);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/FetchObserver.h b/dom/fetch/FetchObserver.h
new file mode 100644
index 0000000000..ba63ed233b
--- /dev/null
+++ b/dom/fetch/FetchObserver.h
@@ -0,0 +1,47 @@
+/* -*- 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_FetchObserver_h
+#define mozilla_dom_FetchObserver_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/FetchObserverBinding.h"
+#include "mozilla/dom/AbortSignal.h"
+
+namespace mozilla {
+namespace dom {
+
+class FetchObserver final : public DOMEventTargetHelper, public AbortFollower {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchObserver, DOMEventTargetHelper)
+
+ FetchObserver(nsIGlobalObject* aGlobal, AbortSignalImpl* aSignalImpl);
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ FetchState State() const;
+
+ IMPL_EVENT_HANDLER(statechange);
+ IMPL_EVENT_HANDLER(requestprogress);
+ IMPL_EVENT_HANDLER(responseprogress);
+
+ // AbortFollower
+ void RunAbortAlgorithm() override;
+
+ void SetState(FetchState aState);
+
+ private:
+ ~FetchObserver() = default;
+
+ FetchState mState;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FetchObserver_h
diff --git a/dom/fetch/FetchStreamReader.cpp b/dom/fetch/FetchStreamReader.cpp
new file mode 100644
index 0000000000..b202dbe430
--- /dev/null
+++ b/dom/fetch/FetchStreamReader.cpp
@@ -0,0 +1,384 @@
+/* -*- 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 "FetchStreamReader.h"
+#include "InternalResponse.h"
+#include "js/Stream.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseBinding.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/TaskCategory.h"
+#include "nsContentUtils.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIPipe.h"
+#include "nsIScriptError.h"
+#include "nsPIDOMWindow.h"
+#include "jsapi.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FetchStreamReader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FetchStreamReader)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FetchStreamReader)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FetchStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FetchStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(FetchStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReader)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchStreamReader)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStreamCallback)
+NS_INTERFACE_MAP_END
+
+/* static */
+nsresult FetchStreamReader::Create(JSContext* aCx, nsIGlobalObject* aGlobal,
+ FetchStreamReader** aStreamReader,
+ nsIInputStream** aInputStream) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aGlobal);
+ MOZ_ASSERT(aStreamReader);
+ MOZ_ASSERT(aInputStream);
+
+ RefPtr<FetchStreamReader> streamReader = new FetchStreamReader(aGlobal);
+
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+
+ nsresult rv =
+ NS_NewPipe2(getter_AddRefs(pipeIn),
+ getter_AddRefs(streamReader->mPipeOut), true, true, 0, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!NS_IsMainThread()) {
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<WeakWorkerRef> workerRef =
+ WeakWorkerRef::Create(workerPrivate, [streamReader]() {
+ MOZ_ASSERT(streamReader);
+ MOZ_ASSERT(streamReader->mWorkerRef);
+
+ WorkerPrivate* workerPrivate = streamReader->mWorkerRef->GetPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ streamReader->CloseAndRelease(workerPrivate->GetJSContext(),
+ NS_ERROR_DOM_INVALID_STATE_ERR);
+ });
+
+ if (NS_WARN_IF(!workerRef)) {
+ streamReader->mPipeOut->CloseWithStatus(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ // These 2 objects create a ref-cycle here that is broken when the stream is
+ // closed or the worker shutsdown.
+ streamReader->mWorkerRef = std::move(workerRef);
+ }
+
+ pipeIn.forget(aInputStream);
+ streamReader.forget(aStreamReader);
+ return NS_OK;
+}
+
+FetchStreamReader::FetchStreamReader(nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal),
+ mOwningEventTarget(mGlobal->EventTargetFor(TaskCategory::Other)),
+ mBufferRemaining(0),
+ mBufferOffset(0),
+ mStreamClosed(false) {
+ MOZ_ASSERT(aGlobal);
+
+ mozilla::HoldJSObjects(this);
+}
+
+FetchStreamReader::~FetchStreamReader() {
+ CloseAndRelease(nullptr, NS_BASE_STREAM_CLOSED);
+
+ mozilla::DropJSObjects(this);
+}
+
+// If a context is provided, an attempt will be made to cancel the reader. The
+// only situation where we don't expect to have a context is when closure is
+// being triggered from the destructor or the WorkerRef is notifying. If
+// we're at the destructor, it's far too late to cancel anything. And if the
+// WorkerRef is being notified, the global is going away, so there's also
+// no need to do further JS work.
+void FetchStreamReader::CloseAndRelease(JSContext* aCx, nsresult aStatus) {
+ NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
+
+ if (mStreamClosed) {
+ // Already closed.
+ return;
+ }
+
+ RefPtr<FetchStreamReader> kungFuDeathGrip = this;
+
+ if (aCx && mReader) {
+ RefPtr<DOMException> error = DOMException::Create(aStatus);
+
+ JS::Rooted<JS::Value> errorValue(aCx);
+ if (ToJSValue(aCx, error, &errorValue)) {
+ JS::Rooted<JSObject*> reader(aCx, mReader);
+ // It's currently safe to cancel an already closed reader because, per the
+ // comments in ReadableStream::cancel() conveying the spec, step 2 of
+ // 3.4.3 that specified ReadableStreamCancel is: If stream.[[state]] is
+ // "closed", return a new promise resolved with undefined.
+ JS::ReadableStreamReaderCancel(aCx, reader, errorValue);
+ }
+
+ // We don't want to propagate exceptions during the cleanup.
+ JS_ClearPendingException(aCx);
+ }
+
+ mStreamClosed = true;
+
+ mGlobal = nullptr;
+
+ mPipeOut->CloseWithStatus(aStatus);
+ mPipeOut = nullptr;
+
+ mWorkerRef = nullptr;
+
+ mReader = nullptr;
+ mBuffer.Clear();
+}
+
+void FetchStreamReader::StartConsuming(JSContext* aCx, JS::HandleObject aStream,
+ JS::MutableHandle<JSObject*> aReader,
+ ErrorResult& aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(!mReader);
+ MOZ_DIAGNOSTIC_ASSERT(aStream);
+
+ aRv.MightThrowJSException();
+
+ // Here, by spec, we can pick any global we want. Just to avoid extra
+ // cross-compartment steps, we want to create the reader in the same
+ // compartment of the owning Fetch Body object.
+ // The same global will be used to retrieve data from this reader.
+ JSAutoRealm ar(aCx, mGlobal->GetGlobalJSObject());
+
+ JS::Rooted<JSObject*> reader(
+ aCx, JS::ReadableStreamGetReader(aCx, aStream,
+ JS::ReadableStreamReaderMode::Default));
+ if (!reader) {
+ aRv.StealExceptionFromJSContext(aCx);
+ CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ mReader = reader;
+ aReader.set(reader);
+
+ aRv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+}
+
+// nsIOutputStreamCallback interface
+
+NS_IMETHODIMP
+FetchStreamReader::OnOutputStreamReady(nsIAsyncOutputStream* aStream) {
+ NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
+ MOZ_ASSERT(aStream == mPipeOut);
+ MOZ_ASSERT(mReader);
+
+ if (mStreamClosed) {
+ return NS_OK;
+ }
+
+ if (!mBuffer.IsEmpty()) {
+ return WriteBuffer();
+ }
+
+ // Here we can retrieve data from the reader using any global we want because
+ // it is not observable. We want to use the reader's global, which is also the
+ // Response's one.
+ AutoEntryScript aes(mGlobal, "ReadableStreamReader.read", !mWorkerRef);
+
+ JS::Rooted<JSObject*> reader(aes.cx(), mReader);
+ JS::Rooted<JSObject*> promise(
+ aes.cx(), JS::ReadableStreamDefaultReaderRead(aes.cx(), reader));
+ if (NS_WARN_IF(!promise)) {
+ // Let's close the stream.
+ CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<Promise> domPromise = Promise::CreateFromExisting(mGlobal, promise);
+ if (NS_WARN_IF(!domPromise)) {
+ // Let's close the stream.
+ CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Let's wait.
+ domPromise->AppendNativeHandler(this);
+ return NS_OK;
+}
+
+void FetchStreamReader::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) {
+ if (mStreamClosed) {
+ return;
+ }
+
+ // This promise should be resolved with { done: boolean, value: something },
+ // "value" is interesting only if done is false.
+
+ // We don't want to play with JS api, let's WebIDL bindings doing it for us.
+ // FetchReadableStreamReadDataDone is a dictionary with just a boolean, if the
+ // parsing succeeded, we can proceed with the parsing of the "value", which it
+ // must be a Uint8Array.
+ FetchReadableStreamReadDataDone valueDone;
+ if (!valueDone.Init(aCx, aValue)) {
+ JS_ClearPendingException(aCx);
+ CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (valueDone.mDone) {
+ // Stream is completed.
+ CloseAndRelease(aCx, NS_BASE_STREAM_CLOSED);
+ return;
+ }
+
+ RootedDictionary<FetchReadableStreamReadDataArray> value(aCx);
+ if (!value.Init(aCx, aValue) || !value.mValue.WasPassed()) {
+ JS_ClearPendingException(aCx);
+ CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ Uint8Array& array = value.mValue.Value();
+ array.ComputeState();
+ uint32_t len = array.Length();
+
+ if (len == 0) {
+ // If there is nothing to read, let's do another reading.
+ OnOutputStreamReady(mPipeOut);
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mBuffer.IsEmpty());
+
+ // Let's take a copy of the data.
+ if (!mBuffer.AppendElements(array.Data(), len, fallible)) {
+ CloseAndRelease(aCx, NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ mBufferOffset = 0;
+ mBufferRemaining = len;
+
+ nsresult rv = WriteBuffer();
+ if (NS_FAILED(rv)) {
+ // DOMException only understands errors from domerr.msg, so we normalize to
+ // identifying an abort if the write fails.
+ CloseAndRelease(aCx, NS_ERROR_DOM_ABORT_ERR);
+ }
+}
+
+nsresult FetchStreamReader::WriteBuffer() {
+ MOZ_ASSERT(!mBuffer.IsEmpty());
+
+ char* data = reinterpret_cast<char*>(mBuffer.Elements());
+
+ while (1) {
+ uint32_t written = 0;
+ nsresult rv =
+ mPipeOut->Write(data + mBufferOffset, mBufferRemaining, &written);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ break;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(written <= mBufferRemaining);
+ mBufferRemaining -= written;
+ mBufferOffset += written;
+
+ if (mBufferRemaining == 0) {
+ mBuffer.Clear();
+ break;
+ }
+ }
+
+ nsresult rv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void FetchStreamReader::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) {
+ ReportErrorToConsole(aCx, aValue);
+ CloseAndRelease(aCx, NS_ERROR_FAILURE);
+}
+
+void FetchStreamReader::ReportErrorToConsole(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) {
+ nsCString sourceSpec;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ nsString valueString;
+
+ nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
+ valueString);
+
+ nsTArray<nsString> params;
+ params.AppendElement(valueString);
+
+ RefPtr<ConsoleReportCollector> reporter = new ConsoleReportCollector();
+ reporter->AddConsoleReport(nsIScriptError::errorFlag,
+ "ReadableStreamReader.read"_ns,
+ nsContentUtils::eDOM_PROPERTIES, sourceSpec, line,
+ column, "ReadableStreamReadingFailed"_ns, params);
+
+ uint64_t innerWindowId = 0;
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
+ if (window) {
+ innerWindowId = window->WindowID();
+ }
+ reporter->FlushReportsToConsole(innerWindowId);
+ return;
+ }
+
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ if (workerPrivate) {
+ innerWindowId = workerPrivate->WindowID();
+ }
+
+ RefPtr<Runnable> r = NS_NewRunnableFunction(
+ "FetchStreamReader::ReportErrorToConsole", [reporter, innerWindowId]() {
+ reporter->FlushReportsToConsole(innerWindowId);
+ });
+
+ workerPrivate->DispatchToMainThread(r.forget());
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/FetchStreamReader.h b/dom/fetch/FetchStreamReader.h
new file mode 100644
index 0000000000..6b28136f8f
--- /dev/null
+++ b/dom/fetch/FetchStreamReader.h
@@ -0,0 +1,76 @@
+/* -*- 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_FetchStreamReader_h
+#define mozilla_dom_FetchStreamReader_h
+
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "mozilla/dom/FetchBinding.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla {
+namespace dom {
+
+class WeakWorkerRef;
+
+class FetchStreamReader final : public nsIOutputStreamCallback,
+ public PromiseNativeHandler {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(
+ FetchStreamReader, nsIOutputStreamCallback)
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ // This creates a nsIInputStream able to retrieve data from the ReadableStream
+ // object. The reading starts when StartConsuming() is called.
+ static nsresult Create(JSContext* aCx, nsIGlobalObject* aGlobal,
+ FetchStreamReader** aStreamReader,
+ nsIInputStream** aInputStream);
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ // Idempotently close the output stream and null out all state. If aCx is
+ // provided, the reader will also be canceled. aStatus must be a DOM error
+ // as understood by DOMException because it will be provided as the
+ // cancellation reason.
+ void CloseAndRelease(JSContext* aCx, nsresult aStatus);
+
+ void StartConsuming(JSContext* aCx, JS::HandleObject aStream,
+ JS::MutableHandle<JSObject*> aReader, ErrorResult& aRv);
+
+ private:
+ explicit FetchStreamReader(nsIGlobalObject* aGlobal);
+ ~FetchStreamReader();
+
+ nsresult WriteBuffer();
+
+ void ReportErrorToConsole(JSContext* aCx, JS::Handle<JS::Value> aValue);
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
+
+ RefPtr<WeakWorkerRef> mWorkerRef;
+
+ JS::Heap<JSObject*> mReader;
+
+ nsTArray<uint8_t> mBuffer;
+ uint32_t mBufferRemaining;
+ uint32_t mBufferOffset;
+
+ bool mStreamClosed;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FetchStreamReader_h
diff --git a/dom/fetch/FetchTypes.ipdlh b/dom/fetch/FetchTypes.ipdlh
new file mode 100644
index 0000000000..a01c5154e5
--- /dev/null
+++ b/dom/fetch/FetchTypes.ipdlh
@@ -0,0 +1,86 @@
+/* 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 IPCStream;
+include ChannelInfo;
+include PBackgroundSharedTypes;
+
+include protocol PRemoteLazyInputStream;
+
+include "mozilla/dom/FetchIPCTypes.h";
+
+using HeadersGuardEnum from "mozilla/dom/HeadersBinding.h";
+using ReferrerPolicy from "mozilla/dom/ReferrerPolicyBinding.h";
+using RequestCache from "mozilla/dom/RequestBinding.h";
+using RequestCredentials from "mozilla/dom/RequestBinding.h";
+using RequestMode from "mozilla/dom/RequestBinding.h";
+using RequestRedirect from "mozilla/dom/RequestBinding.h";
+using ResponseType from "mozilla/dom/ResponseBinding.h";
+using struct nsID from "nsID.h";
+
+namespace mozilla {
+namespace dom {
+
+struct HeadersEntry {
+ nsCString name;
+ nsCString value;
+};
+
+struct ParentToParentStream {
+ // Used as a key for RemoteLazyInputStreamStorage
+ nsID uuid;
+};
+
+struct ParentToChildStream {
+ PRemoteLazyInputStream actor;
+};
+
+struct ChildToParentStream {
+ IPCStream stream;
+};
+
+union BodyStreamVariant {
+ ParentToParentStream;
+ ParentToChildStream;
+ ChildToParentStream;
+};
+
+struct IPCInternalRequest {
+ nsCString method;
+ nsCString[] urlList;
+ HeadersGuardEnum headersGuard;
+ HeadersEntry[] headers;
+ BodyStreamVariant? body;
+ int64_t bodySize;
+ nsCString preferredAlternativeDataType;
+ uint32_t contentPolicyType;
+ nsString referrer;
+ ReferrerPolicy referrerPolicy;
+ RequestMode requestMode;
+ RequestCredentials requestCredentials;
+ RequestCache cacheMode;
+ RequestRedirect requestRedirect;
+ nsString integrity;
+ nsCString fragment;
+ PrincipalInfo? principalInfo;
+};
+
+struct IPCInternalResponse {
+ ResponseType type;
+ nsCString[] urlList;
+ uint16_t status;
+ nsCString statusText;
+ HeadersGuardEnum headersGuard;
+ HeadersEntry[] headers;
+ BodyStreamVariant? body;
+ int64_t bodySize;
+ nsresult errorCode;
+ nsCString alternativeDataType;
+ BodyStreamVariant? alternativeBody;
+ IPCChannelInfo channelInfo;
+ PrincipalInfo? principalInfo;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/dom/fetch/FetchUtil.cpp b/dom/fetch/FetchUtil.cpp
new file mode 100644
index 0000000000..330ef6dda6
--- /dev/null
+++ b/dom/fetch/FetchUtil.cpp
@@ -0,0 +1,569 @@
+/* -*- 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 "FetchUtil.h"
+
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "nsCRT.h"
+#include "nsError.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIHttpChannel.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "mozilla/dom/Document.h"
+
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/InternalRequest.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/WorkerRef.h"
+
+namespace mozilla::dom {
+
+// static
+nsresult FetchUtil::GetValidRequestMethod(const nsACString& aMethod,
+ nsCString& outMethod) {
+ nsAutoCString upperCaseMethod(aMethod);
+ ToUpperCase(upperCaseMethod);
+ if (!NS_IsValidHTTPToken(aMethod)) {
+ outMethod.SetIsVoid(true);
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ if (upperCaseMethod.EqualsLiteral("CONNECT") ||
+ upperCaseMethod.EqualsLiteral("TRACE") ||
+ upperCaseMethod.EqualsLiteral("TRACK")) {
+ outMethod.SetIsVoid(true);
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ if (upperCaseMethod.EqualsLiteral("DELETE") ||
+ upperCaseMethod.EqualsLiteral("GET") ||
+ upperCaseMethod.EqualsLiteral("HEAD") ||
+ upperCaseMethod.EqualsLiteral("OPTIONS") ||
+ upperCaseMethod.EqualsLiteral("POST") ||
+ upperCaseMethod.EqualsLiteral("PUT")) {
+ outMethod = upperCaseMethod;
+ } else {
+ outMethod = aMethod; // Case unchanged for non-standard methods
+ }
+ return NS_OK;
+}
+
+static bool FindCRLF(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd) {
+ nsACString::const_iterator end(aEnd);
+ return FindInReadable("\r\n"_ns, aStart, end);
+}
+
+// Reads over a CRLF and positions start after it.
+static bool PushOverLine(nsACString::const_iterator& aStart,
+ const nsACString::const_iterator& aEnd) {
+ if (*aStart == nsCRT::CR && (aEnd - aStart > 1) && *(++aStart) == nsCRT::LF) {
+ ++aStart; // advance to after CRLF
+ return true;
+ }
+
+ return false;
+}
+
+// static
+bool FetchUtil::ExtractHeader(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd,
+ nsCString& aHeaderName, nsCString& aHeaderValue,
+ bool* aWasEmptyHeader) {
+ MOZ_ASSERT(aWasEmptyHeader);
+ // Set it to a valid value here so we don't forget later.
+ *aWasEmptyHeader = false;
+
+ const char* beginning = aStart.get();
+ nsACString::const_iterator end(aEnd);
+ if (!FindCRLF(aStart, end)) {
+ return false;
+ }
+
+ if (aStart.get() == beginning) {
+ *aWasEmptyHeader = true;
+ return true;
+ }
+
+ nsAutoCString header(beginning, aStart.get() - beginning);
+
+ nsACString::const_iterator headerStart, iter, headerEnd;
+ header.BeginReading(headerStart);
+ header.EndReading(headerEnd);
+ iter = headerStart;
+ if (!FindCharInReadable(':', iter, headerEnd)) {
+ return false;
+ }
+
+ aHeaderName.Assign(StringHead(header, iter - headerStart));
+ aHeaderName.CompressWhitespace();
+ if (!NS_IsValidHTTPToken(aHeaderName)) {
+ return false;
+ }
+
+ aHeaderValue.Assign(Substring(++iter, headerEnd));
+ if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue)) {
+ return false;
+ }
+ aHeaderValue.CompressWhitespace();
+
+ return PushOverLine(aStart, aEnd);
+}
+
+// static
+nsresult FetchUtil::SetRequestReferrer(nsIPrincipal* aPrincipal, Document* aDoc,
+ nsIHttpChannel* aChannel,
+ InternalRequest& aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = NS_OK;
+ nsAutoString referrer;
+ aRequest.GetReferrer(referrer);
+
+ ReferrerPolicy policy = aRequest.ReferrerPolicy_();
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ if (referrer.IsEmpty()) {
+ // This is the case request’s referrer is "no-referrer"
+ referrerInfo = new ReferrerInfo(nullptr, ReferrerPolicy::No_referrer);
+ } else if (referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
+ referrerInfo = ReferrerInfo::CreateForFetch(aPrincipal, aDoc);
+ // In the first step, we should use referrer info from requetInit
+ referrerInfo = static_cast<ReferrerInfo*>(referrerInfo.get())
+ ->CloneWithNewPolicy(policy);
+ } else {
+ // From "Determine request's Referrer" step 3
+ // "If request's referrer is a URL, let referrerSource be request's
+ // referrer."
+ nsCOMPtr<nsIURI> referrerURI;
+ rv = NS_NewURI(getter_AddRefs(referrerURI), referrer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ referrerInfo = new ReferrerInfo(referrerURI, policy);
+ }
+
+ rv = aChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString computedReferrerSpec;
+ referrerInfo = aChannel->GetReferrerInfo();
+ if (referrerInfo) {
+ Unused << referrerInfo->GetComputedReferrerSpec(computedReferrerSpec);
+ }
+
+ // Step 8 https://fetch.spec.whatwg.org/#main-fetch
+ // If request’s referrer is not "no-referrer", set request’s referrer to
+ // the result of invoking determine request’s referrer.
+ aRequest.SetReferrer(computedReferrerSpec);
+
+ return NS_OK;
+}
+
+class WindowStreamOwner final : public nsIObserver,
+ public nsSupportsWeakReference {
+ // Read from any thread but only set/cleared on the main thread. The lifecycle
+ // of WindowStreamOwner prevents concurrent read/clear.
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ ~WindowStreamOwner() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
+ }
+ }
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ WindowStreamOwner(nsIAsyncInputStream* aStream, nsIGlobalObject* aGlobal)
+ : mStream(aStream), mGlobal(aGlobal) {
+ MOZ_DIAGNOSTIC_ASSERT(mGlobal);
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ static already_AddRefed<WindowStreamOwner> Create(
+ nsIAsyncInputStream* aStream, nsIGlobalObject* aGlobal) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!os)) {
+ return nullptr;
+ }
+
+ RefPtr<WindowStreamOwner> self = new WindowStreamOwner(aStream, aGlobal);
+
+ // Holds nsIWeakReference to self.
+ nsresult rv = os->AddObserver(self, DOM_WINDOW_DESTROYED_TOPIC, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ return self.forget();
+ }
+
+ struct Destroyer final : Runnable {
+ RefPtr<WindowStreamOwner> mDoomed;
+
+ explicit Destroyer(already_AddRefed<WindowStreamOwner> aDoomed)
+ : Runnable("WindowStreamOwner::Destroyer"), mDoomed(aDoomed) {}
+
+ NS_IMETHOD
+ Run() override {
+ mDoomed = nullptr;
+ return NS_OK;
+ }
+ };
+
+ // nsIObserver:
+
+ NS_IMETHOD
+ Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0);
+
+ if (!mStream) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
+ if (!SameCOMIdentity(aSubject, window)) {
+ return NS_OK;
+ }
+
+ // mStream->Close() will call JSStreamConsumer::OnInputStreamReady which may
+ // then destory itself, dropping the last reference to 'this'.
+ RefPtr<WindowStreamOwner> keepAlive(this);
+
+ mStream->Close();
+ mStream = nullptr;
+ mGlobal = nullptr;
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(WindowStreamOwner, nsIObserver, nsISupportsWeakReference)
+
+class WorkerStreamOwner final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(WorkerStreamOwner)
+
+ explicit WorkerStreamOwner(nsIAsyncInputStream* aStream) : mStream(aStream) {}
+
+ static already_AddRefed<WorkerStreamOwner> Create(
+ nsIAsyncInputStream* aStream, WorkerPrivate* aWorker) {
+ RefPtr<WorkerStreamOwner> self = new WorkerStreamOwner(aStream);
+
+ self->mWorkerRef = WeakWorkerRef::Create(aWorker, [self]() {
+ if (self->mStream) {
+ // If this Close() calls JSStreamConsumer::OnInputStreamReady and drops
+ // the last reference to the JSStreamConsumer, 'this' will not be
+ // destroyed since ~JSStreamConsumer() only enqueues a Destroyer.
+ self->mStream->Close();
+ self->mStream = nullptr;
+ self->mWorkerRef = nullptr;
+ }
+ });
+
+ if (!self->mWorkerRef) {
+ return nullptr;
+ }
+
+ return self.forget();
+ }
+
+ struct Destroyer final : CancelableRunnable {
+ RefPtr<WorkerStreamOwner> mDoomed;
+
+ explicit Destroyer(RefPtr<WorkerStreamOwner>&& aDoomed)
+ : CancelableRunnable("WorkerStreamOwner::Destroyer"),
+ mDoomed(std::move(aDoomed)) {}
+
+ NS_IMETHOD
+ Run() override {
+ mDoomed = nullptr;
+ return NS_OK;
+ }
+
+ nsresult Cancel() override { return Run(); }
+ };
+
+ private:
+ ~WorkerStreamOwner() = default;
+
+ // Read from any thread but only set/cleared on the worker thread. The
+ // lifecycle of WorkerStreamOwner prevents concurrent read/clear.
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ RefPtr<WeakWorkerRef> mWorkerRef;
+};
+
+class JSStreamConsumer final : public nsIInputStreamCallback {
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+ RefPtr<WindowStreamOwner> mWindowStreamOwner;
+ RefPtr<WorkerStreamOwner> mWorkerStreamOwner;
+ JS::StreamConsumer* mConsumer;
+ bool mConsumerAborted;
+
+ JSStreamConsumer(already_AddRefed<WindowStreamOwner> aWindowStreamOwner,
+ nsIGlobalObject* aGlobal, JS::StreamConsumer* aConsumer)
+ : mOwningEventTarget(aGlobal->EventTargetFor(TaskCategory::Other)),
+ mWindowStreamOwner(aWindowStreamOwner),
+ mConsumer(aConsumer),
+ mConsumerAborted(false) {
+ MOZ_DIAGNOSTIC_ASSERT(mWindowStreamOwner);
+ MOZ_DIAGNOSTIC_ASSERT(mConsumer);
+ }
+
+ JSStreamConsumer(RefPtr<WorkerStreamOwner> aWorkerStreamOwner,
+ nsIGlobalObject* aGlobal, JS::StreamConsumer* aConsumer)
+ : mOwningEventTarget(aGlobal->EventTargetFor(TaskCategory::Other)),
+ mWorkerStreamOwner(std::move(aWorkerStreamOwner)),
+ mConsumer(aConsumer),
+ mConsumerAborted(false) {
+ MOZ_DIAGNOSTIC_ASSERT(mWorkerStreamOwner);
+ MOZ_DIAGNOSTIC_ASSERT(mConsumer);
+ }
+
+ ~JSStreamConsumer() {
+ // Both WindowStreamOwner and WorkerStreamOwner need to be destroyed on
+ // their global's event target thread.
+
+ RefPtr<Runnable> destroyer;
+ if (mWindowStreamOwner) {
+ MOZ_DIAGNOSTIC_ASSERT(!mWorkerStreamOwner);
+ destroyer = new WindowStreamOwner::Destroyer(mWindowStreamOwner.forget());
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(mWorkerStreamOwner);
+ destroyer =
+ new WorkerStreamOwner::Destroyer(std::move(mWorkerStreamOwner));
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(destroyer.forget()));
+ }
+
+ static nsresult WriteSegment(nsIInputStream* aStream, void* aClosure,
+ const char* aFromSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount) {
+ JSStreamConsumer* self = reinterpret_cast<JSStreamConsumer*>(aClosure);
+ MOZ_DIAGNOSTIC_ASSERT(!self->mConsumerAborted);
+
+ // This callback can be called on any thread which is explicitly allowed by
+ // this particular JS API call.
+ if (!self->mConsumer->consumeChunk((const uint8_t*)aFromSegment, aCount)) {
+ self->mConsumerAborted = true;
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aWriteCount = aCount;
+ return NS_OK;
+ }
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ static bool Start(nsCOMPtr<nsIInputStream>&& aStream,
+ JS::StreamConsumer* aConsumer, nsIGlobalObject* aGlobal,
+ WorkerPrivate* aMaybeWorker) {
+ nsCOMPtr<nsIAsyncInputStream> asyncStream;
+ nsresult rv = NS_MakeAsyncNonBlockingInputStream(
+ aStream.forget(), getter_AddRefs(asyncStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ RefPtr<JSStreamConsumer> consumer;
+ if (aMaybeWorker) {
+ RefPtr<WorkerStreamOwner> owner =
+ WorkerStreamOwner::Create(asyncStream, aMaybeWorker);
+ if (!owner) {
+ return false;
+ }
+
+ consumer = new JSStreamConsumer(std::move(owner), aGlobal, aConsumer);
+ } else {
+ RefPtr<WindowStreamOwner> owner =
+ WindowStreamOwner::Create(asyncStream, aGlobal);
+ if (!owner) {
+ return false;
+ }
+
+ consumer = new JSStreamConsumer(owner.forget(), aGlobal, aConsumer);
+ }
+
+ // This AsyncWait() creates a ref-cycle between asyncStream and consumer:
+ //
+ // asyncStream -> consumer -> (Window|Worker)StreamOwner -> asyncStream
+ //
+ // The cycle is broken when the stream completes or errors out and
+ // asyncStream drops its reference to consumer.
+ return NS_SUCCEEDED(asyncStream->AsyncWait(consumer, 0, 0, nullptr));
+ }
+
+ // nsIInputStreamCallback:
+
+ NS_IMETHOD
+ OnInputStreamReady(nsIAsyncInputStream* aStream) override {
+ // Can be called on any stream. The JS API calls made below explicitly
+ // support being called from any thread.
+ MOZ_DIAGNOSTIC_ASSERT(!mConsumerAborted);
+
+ nsresult rv;
+
+ uint64_t available = 0;
+ rv = aStream->Available(&available);
+ if (NS_SUCCEEDED(rv) && available == 0) {
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ mConsumer->streamEnd();
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ mConsumer->streamError(size_t(rv));
+ return NS_OK;
+ }
+
+ // Check mConsumerAborted before NS_FAILED to avoid calling streamError()
+ // if consumeChunk() returned false per JS API contract.
+ uint32_t written = 0;
+ rv = aStream->ReadSegments(WriteSegment, this, available, &written);
+ if (mConsumerAborted) {
+ return NS_OK;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mConsumer->streamError(size_t(rv));
+ return NS_OK;
+ }
+
+ rv = aStream->AsyncWait(this, 0, 0, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mConsumer->streamError(size_t(rv));
+ return NS_OK;
+ }
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(JSStreamConsumer, nsIInputStreamCallback)
+
+static bool ThrowException(JSContext* aCx, unsigned errorNumber) {
+ JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, errorNumber);
+ return false;
+}
+
+// static
+bool FetchUtil::StreamResponseToJS(JSContext* aCx, JS::HandleObject aObj,
+ JS::MimeType aMimeType,
+ JS::StreamConsumer* aConsumer,
+ WorkerPrivate* aMaybeWorker) {
+ MOZ_ASSERT(!aMaybeWorker == NS_IsMainThread());
+
+ RefPtr<Response> response;
+ nsresult rv = UNWRAP_OBJECT(Response, aObj, response);
+ if (NS_FAILED(rv)) {
+ return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_VALUE);
+ }
+
+ const char* requiredMimeType = nullptr;
+ switch (aMimeType) {
+ case JS::MimeType::Wasm:
+ requiredMimeType = WASM_CONTENT_TYPE;
+ break;
+ }
+
+ nsAutoCString mimeType;
+ response->GetMimeType(mimeType);
+
+ if (!mimeType.EqualsASCII(requiredMimeType)) {
+ JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
+ JSMSG_WASM_BAD_RESPONSE_MIME_TYPE, mimeType.get(),
+ requiredMimeType);
+ return false;
+ }
+
+ if (response->Type() != ResponseType::Basic &&
+ response->Type() != ResponseType::Cors &&
+ response->Type() != ResponseType::Default) {
+ return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_CORS_SAME_ORIGIN);
+ }
+
+ if (!response->Ok()) {
+ return ThrowException(aCx, JSMSG_WASM_BAD_RESPONSE_STATUS);
+ }
+
+ IgnoredErrorResult result;
+ bool used = response->GetBodyUsed(result);
+ if (NS_WARN_IF(result.Failed())) {
+ return ThrowException(aCx, JSMSG_WASM_ERROR_CONSUMING_RESPONSE);
+ }
+ if (used) {
+ return ThrowException(aCx, JSMSG_WASM_RESPONSE_ALREADY_CONSUMED);
+ }
+
+ switch (aMimeType) {
+ case JS::MimeType::Wasm:
+ nsAutoString url;
+ response->GetUrl(url);
+
+ nsCString sourceMapUrl;
+ response->GetInternalHeaders()->Get("SourceMap"_ns, sourceMapUrl, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return ThrowException(aCx, JSMSG_WASM_ERROR_CONSUMING_RESPONSE);
+ }
+ NS_ConvertUTF16toUTF8 urlUTF8(url);
+ aConsumer->noteResponseURLs(
+ urlUTF8.get(), sourceMapUrl.IsVoid() ? nullptr : sourceMapUrl.get());
+ break;
+ }
+
+ RefPtr<InternalResponse> ir = response->GetInternalResponse();
+ if (NS_WARN_IF(!ir)) {
+ return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
+ }
+
+ nsCOMPtr<nsIInputStream> body;
+ ir->GetUnfilteredBody(getter_AddRefs(body));
+ if (!body) {
+ aConsumer->streamEnd();
+ return true;
+ }
+
+ IgnoredErrorResult error;
+ response->SetBodyUsed(aCx, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return ThrowException(aCx, JSMSG_WASM_ERROR_CONSUMING_RESPONSE);
+ }
+
+ nsIGlobalObject* global = xpc::NativeGlobal(js::UncheckedUnwrap(aObj));
+
+ if (!JSStreamConsumer::Start(std::move(body), aConsumer, global,
+ aMaybeWorker)) {
+ return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
+ }
+
+ return true;
+}
+
+// static
+void FetchUtil::ReportJSStreamError(JSContext* aCx, size_t aErrorCode) {
+ // For now, convert *all* errors into AbortError.
+
+ RefPtr<DOMException> e = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, e, &value)) {
+ return;
+ }
+
+ JS_SetPendingException(aCx, value);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/FetchUtil.h b/dom/fetch/FetchUtil.h
new file mode 100644
index 0000000000..5907e209a4
--- /dev/null
+++ b/dom/fetch/FetchUtil.h
@@ -0,0 +1,77 @@
+/* -*- 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_FetchUtil_h
+#define mozilla_dom_FetchUtil_h
+
+#include "nsString.h"
+#include "nsError.h"
+
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+
+#define WASM_CONTENT_TYPE "application/wasm"
+#define WASM_ALT_DATA_TYPE_V1 "wasm/machine-code/1"
+
+class nsIPrincipal;
+class nsIHttpChannel;
+
+namespace mozilla {
+namespace dom {
+
+class Document;
+class InternalRequest;
+class WorkerPrivate;
+
+class FetchUtil final {
+ private:
+ FetchUtil() = delete;
+
+ public:
+ /**
+ * Sets outMethod to a valid HTTP request method string based on an input
+ * method. Implements checks and normalization as specified by the Fetch
+ * specification. Returns NS_ERROR_DOM_SECURITY_ERR if the method is invalid.
+ * Otherwise returns NS_OK and the normalized method via outMethod.
+ */
+ static nsresult GetValidRequestMethod(const nsACString& aMethod,
+ nsCString& outMethod);
+
+ /**
+ * Extracts an HTTP header from a substring range.
+ */
+ static bool ExtractHeader(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd,
+ nsCString& aHeaderName, nsCString& aHeaderValue,
+ bool* aWasEmptyHeader);
+
+ static nsresult SetRequestReferrer(nsIPrincipal* aPrincipal, Document* aDoc,
+ nsIHttpChannel* aChannel,
+ InternalRequest& aRequest);
+
+ /**
+ * Check that the given object is a Response and, if so, stream to the given
+ * JS consumer. On any failure, this function will report an error on the
+ * given JSContext before returning false. If executing in a worker, the
+ * WorkerPrivate must be given.
+ */
+ static bool StreamResponseToJS(JSContext* aCx, JS::HandleObject aObj,
+ JS::MimeType aMimeType,
+ JS::StreamConsumer* aConsumer,
+ WorkerPrivate* aMaybeWorker);
+
+ /**
+ * Called by JS to report (i.e., throw) an error that was passed to the
+ * JS::StreamConsumer::streamError() method on a random stream thread.
+ * This method is passed by function pointer to the JS engine hence the
+ * untyped 'size_t' instead of Gecko 'nsresult'.
+ */
+ static void ReportJSStreamError(JSContext* aCx, size_t aErrorCode);
+};
+
+} // namespace dom
+} // namespace mozilla
+#endif
diff --git a/dom/fetch/Headers.cpp b/dom/fetch/Headers.cpp
new file mode 100644
index 0000000000..b8edbe1ed7
--- /dev/null
+++ b/dom/fetch/Headers.cpp
@@ -0,0 +1,87 @@
+/* -*- 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 "mozilla/dom/Headers.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Headers)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Headers)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Headers, mOwner)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Headers)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+// static
+already_AddRefed<Headers> Headers::Constructor(
+ const GlobalObject& aGlobal,
+ const Optional<ByteStringSequenceSequenceOrByteStringByteStringRecord>&
+ aInit,
+ ErrorResult& aRv) {
+ RefPtr<InternalHeaders> ih = new InternalHeaders();
+ RefPtr<Headers> headers = new Headers(aGlobal.GetAsSupports(), ih);
+
+ if (!aInit.WasPassed()) {
+ return headers.forget();
+ }
+
+ if (aInit.Value().IsByteStringSequenceSequence()) {
+ ih->Fill(aInit.Value().GetAsByteStringSequenceSequence(), aRv);
+ } else if (aInit.Value().IsByteStringByteStringRecord()) {
+ ih->Fill(aInit.Value().GetAsByteStringByteStringRecord(), aRv);
+ }
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return headers.forget();
+}
+
+// static
+already_AddRefed<Headers> Headers::Constructor(
+ const GlobalObject& aGlobal,
+ const OwningByteStringSequenceSequenceOrByteStringByteStringRecord& aInit,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ return Create(global, aInit, aRv);
+}
+
+/* static */
+already_AddRefed<Headers> Headers::Create(
+ nsIGlobalObject* aGlobal,
+ const OwningByteStringSequenceSequenceOrByteStringByteStringRecord& aInit,
+ ErrorResult& aRv) {
+ RefPtr<InternalHeaders> ih = new InternalHeaders();
+ RefPtr<Headers> headers = new Headers(aGlobal, ih);
+
+ if (aInit.IsByteStringSequenceSequence()) {
+ ih->Fill(aInit.GetAsByteStringSequenceSequence(), aRv);
+ } else if (aInit.IsByteStringByteStringRecord()) {
+ ih->Fill(aInit.GetAsByteStringByteStringRecord(), aRv);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return headers.forget();
+}
+
+JSObject* Headers::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return mozilla::dom::Headers_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+Headers::~Headers() = default;
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/Headers.h b/dom/fetch/Headers.h
new file mode 100644
index 0000000000..fdccb15406
--- /dev/null
+++ b/dom/fetch/Headers.h
@@ -0,0 +1,128 @@
+/* -*- 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_Headers_h
+#define mozilla_dom_Headers_h
+
+#include "mozilla/dom/HeadersBinding.h"
+
+#include "nsClassHashtable.h"
+#include "nsWrapperCache.h"
+
+#include "InternalHeaders.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+template <typename K, typename V>
+class Record;
+class ByteStringSequenceSequenceOrByteStringByteStringRecord;
+class OwningByteStringSequenceSequenceOrByteStringByteStringRecord;
+
+/**
+ * This Headers class is only used to represent the content facing Headers
+ * object. It is actually backed by an InternalHeaders implementation. Gecko
+ * code should NEVER use this, except in the Request and Response
+ * implementations, where they must always be created from the backing
+ * InternalHeaders object.
+ */
+class Headers final : public nsISupports, public nsWrapperCache {
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Headers)
+
+ friend class Request;
+ friend class Response;
+
+ private:
+ nsCOMPtr<nsISupports> mOwner;
+ RefPtr<InternalHeaders> mInternalHeaders;
+
+ public:
+ explicit Headers(nsISupports* aOwner, InternalHeaders* aInternalHeaders)
+ : mOwner(aOwner), mInternalHeaders(aInternalHeaders) {}
+
+ explicit Headers(const Headers& aOther) = delete;
+
+ static bool PrefEnabled(JSContext* cx, JSObject* obj);
+
+ static already_AddRefed<Headers> Constructor(
+ const GlobalObject& aGlobal,
+ const Optional<ByteStringSequenceSequenceOrByteStringByteStringRecord>&
+ aInit,
+ ErrorResult& aRv);
+
+ static already_AddRefed<Headers> Constructor(
+ const GlobalObject& aGlobal,
+ const OwningByteStringSequenceSequenceOrByteStringByteStringRecord& aInit,
+ ErrorResult& aRv);
+
+ static already_AddRefed<Headers> Create(
+ nsIGlobalObject* aGlobalObject,
+ const OwningByteStringSequenceSequenceOrByteStringByteStringRecord& aInit,
+ ErrorResult& aRv);
+
+ void Append(const nsACString& aName, const nsACString& aValue,
+ ErrorResult& aRv) {
+ mInternalHeaders->Append(aName, aValue, aRv);
+ }
+
+ void Delete(const nsACString& aName, ErrorResult& aRv) {
+ mInternalHeaders->Delete(aName, aRv);
+ }
+
+ void Get(const nsACString& aName, nsACString& aValue,
+ ErrorResult& aRv) const {
+ mInternalHeaders->Get(aName, aValue, aRv);
+ }
+
+ void GetFirst(const nsACString& aName, nsACString& aValue,
+ ErrorResult& aRv) const {
+ mInternalHeaders->GetFirst(aName, aValue, aRv);
+ }
+
+ bool Has(const nsACString& aName, ErrorResult& aRv) const {
+ return mInternalHeaders->Has(aName, aRv);
+ }
+
+ void Set(const nsACString& aName, const nsACString& aValue,
+ ErrorResult& aRv) {
+ mInternalHeaders->Set(aName, aValue, aRv);
+ }
+
+ uint32_t GetIterableLength() const {
+ return mInternalHeaders->GetIterableLength();
+ }
+ const nsString GetKeyAtIndex(unsigned aIndex) const {
+ return mInternalHeaders->GetKeyAtIndex(aIndex);
+ }
+ const nsString GetValueAtIndex(unsigned aIndex) const {
+ return mInternalHeaders->GetValueAtIndex(aIndex);
+ }
+
+ // ChromeOnly
+ HeadersGuardEnum Guard() const { return mInternalHeaders->Guard(); }
+
+ void SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv) {
+ mInternalHeaders->SetGuard(aGuard, aRv);
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ private:
+ virtual ~Headers();
+
+ InternalHeaders* GetInternalHeaders() const { return mInternalHeaders; }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Headers_h
diff --git a/dom/fetch/InternalHeaders.cpp b/dom/fetch/InternalHeaders.cpp
new file mode 100644
index 0000000000..59271d0f86
--- /dev/null
+++ b/dom/fetch/InternalHeaders.cpp
@@ -0,0 +1,637 @@
+/* -*- 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 "mozilla/dom/InternalHeaders.h"
+
+#include "mozilla/dom/FetchTypes.h"
+#include "mozilla/ErrorResult.h"
+
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+
+namespace mozilla::dom {
+
+InternalHeaders::InternalHeaders(nsTArray<Entry>&& aHeaders,
+ HeadersGuardEnum aGuard)
+ : mGuard(aGuard), mList(std::move(aHeaders)), mListDirty(true) {}
+
+InternalHeaders::InternalHeaders(
+ const nsTArray<HeadersEntry>& aHeadersEntryList, HeadersGuardEnum aGuard)
+ : mGuard(aGuard), mListDirty(true) {
+ for (const HeadersEntry& headersEntry : aHeadersEntryList) {
+ mList.AppendElement(Entry(headersEntry.name(), headersEntry.value()));
+ }
+}
+
+void InternalHeaders::ToIPC(nsTArray<HeadersEntry>& aIPCHeaders,
+ HeadersGuardEnum& aGuard) {
+ aGuard = mGuard;
+
+ aIPCHeaders.Clear();
+ for (Entry& entry : mList) {
+ aIPCHeaders.AppendElement(HeadersEntry(entry.mName, entry.mValue));
+ }
+}
+
+bool InternalHeaders::IsValidHeaderValue(const nsCString& aLowerName,
+ const nsCString& aNormalizedValue,
+ ErrorResult& aRv) {
+ // Steps 2 to 6 for ::Set() and ::Append() in the spec.
+
+ // Step 2
+ if (IsInvalidName(aLowerName, aRv) || IsInvalidValue(aNormalizedValue, aRv)) {
+ return false;
+ }
+
+ // Step 3
+ if (IsImmutable(aRv)) {
+ return false;
+ }
+
+ // Step 4
+ if (mGuard == HeadersGuardEnum::Request &&
+ IsForbiddenRequestHeader(aLowerName)) {
+ return false;
+ }
+
+ // Step 5
+ if (mGuard == HeadersGuardEnum::Request_no_cors) {
+ nsAutoCString tempValue;
+ Get(aLowerName, tempValue, aRv);
+
+ if (tempValue.IsVoid()) {
+ tempValue = aNormalizedValue;
+ } else {
+ tempValue.Append(", ");
+ tempValue.Append(aNormalizedValue);
+ }
+
+ if (!nsContentUtils::IsCORSSafelistedRequestHeader(aLowerName, tempValue)) {
+ return false;
+ }
+ }
+
+ // Step 6
+ else if (IsForbiddenResponseHeader(aLowerName)) {
+ return false;
+ }
+
+ return true;
+}
+
+void InternalHeaders::Append(const nsACString& aName, const nsACString& aValue,
+ ErrorResult& aRv) {
+ // Step 1
+ nsAutoCString trimValue;
+ NS_TrimHTTPWhitespace(aValue, trimValue);
+
+ // Steps 2 to 6
+ nsAutoCString lowerName;
+ ToLowerCase(aName, lowerName);
+ if (!IsValidHeaderValue(lowerName, trimValue, aRv)) {
+ return;
+ }
+
+ // Step 7
+ nsAutoCString name(aName);
+ ReuseExistingNameIfExists(name);
+ SetListDirty();
+ mList.AppendElement(Entry(name, trimValue));
+
+ // Step 8
+ if (mGuard == HeadersGuardEnum::Request_no_cors) {
+ RemovePrivilegedNoCorsRequestHeaders();
+ }
+}
+
+void InternalHeaders::RemovePrivilegedNoCorsRequestHeaders() {
+ bool dirty = false;
+
+ // remove in reverse order to minimize copying
+ for (int32_t i = mList.Length() - 1; i >= 0; --i) {
+ if (IsPrivilegedNoCorsRequestHeaderName(mList[i].mName)) {
+ mList.RemoveElementAt(i);
+ dirty = true;
+ }
+ }
+
+ if (dirty) {
+ SetListDirty();
+ }
+}
+
+bool InternalHeaders::DeleteInternal(const nsCString& aLowerName,
+ ErrorResult& aRv) {
+ bool dirty = false;
+
+ // remove in reverse order to minimize copying
+ for (int32_t i = mList.Length() - 1; i >= 0; --i) {
+ if (mList[i].mName.EqualsIgnoreCase(aLowerName.get())) {
+ mList.RemoveElementAt(i);
+ dirty = true;
+ }
+ }
+
+ if (dirty) {
+ SetListDirty();
+ }
+
+ return dirty;
+}
+
+void InternalHeaders::Delete(const nsACString& aName, ErrorResult& aRv) {
+ nsAutoCString lowerName;
+ ToLowerCase(aName, lowerName);
+
+ // Step 1
+ if (IsInvalidName(lowerName, aRv)) {
+ return;
+ }
+
+ // Step 2
+ if (IsImmutable(aRv)) {
+ return;
+ }
+
+ // Step 3
+ if (IsForbiddenRequestHeader(lowerName)) {
+ return;
+ }
+
+ // Step 4
+ if (mGuard == HeadersGuardEnum::Request_no_cors &&
+ !IsNoCorsSafelistedRequestHeaderName(lowerName) &&
+ !IsPrivilegedNoCorsRequestHeaderName(lowerName)) {
+ return;
+ }
+
+ // Step 5
+ if (IsForbiddenResponseHeader(lowerName)) {
+ return;
+ }
+
+ // Steps 6 and 7
+ if (!DeleteInternal(lowerName, aRv)) {
+ return;
+ }
+
+ // Step 8
+ if (mGuard == HeadersGuardEnum::Request_no_cors) {
+ RemovePrivilegedNoCorsRequestHeaders();
+ }
+}
+
+void InternalHeaders::Get(const nsACString& aName, nsACString& aValue,
+ ErrorResult& aRv) const {
+ nsAutoCString lowerName;
+ ToLowerCase(aName, lowerName);
+
+ if (IsInvalidName(lowerName, aRv)) {
+ return;
+ }
+
+ GetInternal(lowerName, aValue, aRv);
+}
+
+void InternalHeaders::GetInternal(const nsCString& aLowerName,
+ nsACString& aValue, ErrorResult& aRv) const {
+ const char* delimiter = ", ";
+ bool firstValueFound = false;
+
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ if (mList[i].mName.EqualsIgnoreCase(aLowerName.get())) {
+ if (firstValueFound) {
+ aValue += delimiter;
+ }
+ aValue += mList[i].mValue;
+ firstValueFound = true;
+ }
+ }
+
+ // No value found, so return null to content
+ if (!firstValueFound) {
+ aValue.SetIsVoid(true);
+ }
+}
+
+void InternalHeaders::GetFirst(const nsACString& aName, nsACString& aValue,
+ ErrorResult& aRv) const {
+ nsAutoCString lowerName;
+ ToLowerCase(aName, lowerName);
+
+ if (IsInvalidName(lowerName, aRv)) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) {
+ aValue = mList[i].mValue;
+ return;
+ }
+ }
+
+ // No value found, so return null to content
+ aValue.SetIsVoid(true);
+}
+
+bool InternalHeaders::Has(const nsACString& aName, ErrorResult& aRv) const {
+ nsAutoCString lowerName;
+ ToLowerCase(aName, lowerName);
+
+ if (IsInvalidName(lowerName, aRv)) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void InternalHeaders::Set(const nsACString& aName, const nsACString& aValue,
+ ErrorResult& aRv) {
+ // Step 1
+ nsAutoCString trimValue;
+ NS_TrimHTTPWhitespace(aValue, trimValue);
+
+ // Steps 2 to 6
+ nsAutoCString lowerName;
+ ToLowerCase(aName, lowerName);
+ if (!IsValidHeaderValue(lowerName, trimValue, aRv)) {
+ return;
+ }
+
+ // Step 7
+ SetListDirty();
+
+ int32_t firstIndex = INT32_MAX;
+
+ // remove in reverse order to minimize copying
+ for (int32_t i = mList.Length() - 1; i >= 0; --i) {
+ if (mList[i].mName.EqualsIgnoreCase(lowerName.get())) {
+ firstIndex = std::min(firstIndex, i);
+ mList.RemoveElementAt(i);
+ }
+ }
+
+ if (firstIndex < INT32_MAX) {
+ Entry* entry = mList.InsertElementAt(firstIndex);
+ entry->mName = aName;
+ entry->mValue = trimValue;
+ } else {
+ mList.AppendElement(Entry(aName, trimValue));
+ }
+
+ // Step 8
+ if (mGuard == HeadersGuardEnum::Request_no_cors) {
+ RemovePrivilegedNoCorsRequestHeaders();
+ }
+}
+
+void InternalHeaders::Clear() {
+ SetListDirty();
+ mList.Clear();
+}
+
+void InternalHeaders::SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv) {
+ // The guard is only checked during ::Set() and ::Append() in the spec. It
+ // does not require revalidating headers already set.
+ mGuard = aGuard;
+}
+
+InternalHeaders::~InternalHeaders() = default;
+
+// static
+bool InternalHeaders::IsNoCorsSafelistedRequestHeaderName(
+ const nsCString& aName) {
+ return aName.EqualsIgnoreCase("accept") ||
+ aName.EqualsIgnoreCase("accept-language") ||
+ aName.EqualsIgnoreCase("content-language") ||
+ aName.EqualsIgnoreCase("content-type");
+}
+
+// static
+bool InternalHeaders::IsPrivilegedNoCorsRequestHeaderName(
+ const nsCString& aName) {
+ return aName.EqualsIgnoreCase("range");
+}
+
+// static
+bool InternalHeaders::IsSimpleHeader(const nsCString& aName,
+ const nsACString& aValue) {
+ if (aValue.Length() > 128) {
+ return false;
+ }
+ // Note, we must allow a null content-type value here to support
+ // get("content-type"), but the IsInvalidValue() check will prevent null
+ // from being set or appended.
+ return (aName.EqualsIgnoreCase("accept") &&
+ nsContentUtils::IsAllowedNonCorsAccept(aValue)) ||
+ (aName.EqualsIgnoreCase("accept-language") &&
+ nsContentUtils::IsAllowedNonCorsLanguage(aValue)) ||
+ (aName.EqualsIgnoreCase("content-language") &&
+ nsContentUtils::IsAllowedNonCorsLanguage(aValue)) ||
+ (aName.EqualsIgnoreCase("content-type") &&
+ nsContentUtils::IsAllowedNonCorsContentType(aValue));
+}
+
+// static
+bool InternalHeaders::IsRevalidationHeader(const nsCString& aName) {
+ return aName.EqualsIgnoreCase("if-modified-since") ||
+ aName.EqualsIgnoreCase("if-none-match") ||
+ aName.EqualsIgnoreCase("if-unmodified-since") ||
+ aName.EqualsIgnoreCase("if-match") ||
+ aName.EqualsIgnoreCase("if-range");
+}
+
+// static
+bool InternalHeaders::IsInvalidName(const nsACString& aName, ErrorResult& aRv) {
+ if (!NS_IsValidHTTPToken(aName)) {
+ aRv.ThrowTypeError<MSG_INVALID_HEADER_NAME>(aName);
+ return true;
+ }
+
+ return false;
+}
+
+// static
+bool InternalHeaders::IsInvalidValue(const nsACString& aValue,
+ ErrorResult& aRv) {
+ if (!NS_IsReasonableHTTPHeaderValue(aValue)) {
+ aRv.ThrowTypeError<MSG_INVALID_HEADER_VALUE>(aValue);
+ return true;
+ }
+ return false;
+}
+
+bool InternalHeaders::IsImmutable(ErrorResult& aRv) const {
+ if (mGuard == HeadersGuardEnum::Immutable) {
+ aRv.ThrowTypeError("Headers are immutable and cannot be modified.");
+ return true;
+ }
+ return false;
+}
+
+bool InternalHeaders::IsForbiddenRequestHeader(const nsCString& aName) const {
+ return mGuard == HeadersGuardEnum::Request &&
+ nsContentUtils::IsForbiddenRequestHeader(aName);
+}
+
+bool InternalHeaders::IsForbiddenRequestNoCorsHeader(
+ const nsCString& aName) const {
+ return mGuard == HeadersGuardEnum::Request_no_cors &&
+ !IsSimpleHeader(aName, ""_ns);
+}
+
+bool InternalHeaders::IsForbiddenRequestNoCorsHeader(
+ const nsCString& aName, const nsACString& aValue) const {
+ return mGuard == HeadersGuardEnum::Request_no_cors &&
+ !IsSimpleHeader(aName, aValue);
+}
+
+bool InternalHeaders::IsForbiddenResponseHeader(const nsCString& aName) const {
+ return mGuard == HeadersGuardEnum::Response &&
+ nsContentUtils::IsForbiddenResponseHeader(aName);
+}
+
+void InternalHeaders::Fill(const InternalHeaders& aInit, ErrorResult& aRv) {
+ const nsTArray<Entry>& list = aInit.mList;
+ for (uint32_t i = 0; i < list.Length() && !aRv.Failed(); ++i) {
+ const Entry& entry = list[i];
+ Append(entry.mName, entry.mValue, aRv);
+ }
+}
+
+void InternalHeaders::Fill(const Sequence<Sequence<nsCString>>& aInit,
+ ErrorResult& aRv) {
+ for (uint32_t i = 0; i < aInit.Length() && !aRv.Failed(); ++i) {
+ const Sequence<nsCString>& tuple = aInit[i];
+ if (tuple.Length() != 2) {
+ aRv.ThrowTypeError(
+ "Headers require name/value tuples when being initialized by a "
+ "sequence.");
+ return;
+ }
+ Append(tuple[0], tuple[1], aRv);
+ }
+}
+
+void InternalHeaders::Fill(const Record<nsCString, nsCString>& aInit,
+ ErrorResult& aRv) {
+ for (auto& entry : aInit.Entries()) {
+ Append(entry.mKey, entry.mValue, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+}
+
+namespace {
+
+class FillHeaders final : public nsIHttpHeaderVisitor {
+ RefPtr<InternalHeaders> mInternalHeaders;
+
+ ~FillHeaders() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit FillHeaders(InternalHeaders* aInternalHeaders)
+ : mInternalHeaders(aInternalHeaders) {
+ MOZ_DIAGNOSTIC_ASSERT(mInternalHeaders);
+ }
+
+ NS_IMETHOD
+ VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
+ mInternalHeaders->Append(aHeader, aValue, IgnoreErrors());
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(FillHeaders, nsIHttpHeaderVisitor)
+
+} // namespace
+
+void InternalHeaders::FillResponseHeaders(nsIRequest* aRequest) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (!httpChannel) {
+ return;
+ }
+
+ RefPtr<FillHeaders> visitor = new FillHeaders(this);
+ nsresult rv = httpChannel->VisitResponseHeaders(visitor);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to fill headers");
+ }
+}
+
+bool InternalHeaders::HasOnlySimpleHeaders() const {
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ if (!IsSimpleHeader(mList[i].mName, mList[i].mValue)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool InternalHeaders::HasRevalidationHeaders() const {
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ if (IsRevalidationHeader(mList[i].mName)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// static
+already_AddRefed<InternalHeaders> InternalHeaders::BasicHeaders(
+ InternalHeaders* aHeaders) {
+ RefPtr<InternalHeaders> basic = new InternalHeaders(*aHeaders);
+ ErrorResult result;
+ // The Set-Cookie headers cannot be invalid mutable headers, so the Delete
+ // must succeed.
+ basic->Delete("Set-Cookie"_ns, result);
+ MOZ_ASSERT(!result.Failed());
+ basic->Delete("Set-Cookie2"_ns, result);
+ MOZ_ASSERT(!result.Failed());
+ return basic.forget();
+}
+
+// static
+already_AddRefed<InternalHeaders> InternalHeaders::CORSHeaders(
+ InternalHeaders* aHeaders, RequestCredentials aCredentialsMode) {
+ RefPtr<InternalHeaders> cors = new InternalHeaders(aHeaders->mGuard);
+ ErrorResult result;
+
+ nsAutoCString acExposedNames;
+ aHeaders->Get("Access-Control-Expose-Headers"_ns, acExposedNames, result);
+ MOZ_ASSERT(!result.Failed());
+
+ bool allowAllHeaders = false;
+ AutoTArray<nsCString, 5> exposeNamesArray;
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(acExposedNames, ',').ToRange()) {
+ if (token.IsEmpty()) {
+ continue;
+ }
+
+ if (!NS_IsValidHTTPToken(token)) {
+ NS_WARNING(
+ "Got invalid HTTP token in Access-Control-Expose-Headers. Header "
+ "value is:");
+ NS_WARNING(acExposedNames.get());
+ exposeNamesArray.Clear();
+ break;
+ }
+
+ if (token.EqualsLiteral("*") &&
+ aCredentialsMode != RequestCredentials::Include) {
+ allowAllHeaders = true;
+ }
+
+ exposeNamesArray.AppendElement(token);
+ }
+
+ nsCaseInsensitiveCStringArrayComparator comp;
+ for (uint32_t i = 0; i < aHeaders->mList.Length(); ++i) {
+ const Entry& entry = aHeaders->mList[i];
+ if (allowAllHeaders) {
+ cors->Append(entry.mName, entry.mValue, result);
+ MOZ_ASSERT(!result.Failed());
+ } else if (entry.mName.EqualsIgnoreCase("cache-control") ||
+ entry.mName.EqualsIgnoreCase("content-language") ||
+ entry.mName.EqualsIgnoreCase("content-type") ||
+ entry.mName.EqualsIgnoreCase("expires") ||
+ entry.mName.EqualsIgnoreCase("last-modified") ||
+ entry.mName.EqualsIgnoreCase("pragma") ||
+ exposeNamesArray.Contains(entry.mName, comp)) {
+ cors->Append(entry.mName, entry.mValue, result);
+ MOZ_ASSERT(!result.Failed());
+ }
+ }
+
+ return cors.forget();
+}
+
+void InternalHeaders::GetEntries(
+ nsTArray<InternalHeaders::Entry>& aEntries) const {
+ MOZ_ASSERT(aEntries.IsEmpty());
+ aEntries.AppendElements(mList);
+}
+
+void InternalHeaders::GetUnsafeHeaders(nsTArray<nsCString>& aNames) const {
+ MOZ_ASSERT(aNames.IsEmpty());
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ const Entry& header = mList[i];
+ if (!InternalHeaders::IsSimpleHeader(header.mName, header.mValue)) {
+ aNames.AppendElement(header.mName);
+ }
+ }
+}
+
+void InternalHeaders::MaybeSortList() {
+ class Comparator {
+ public:
+ bool Equals(const Entry& aA, const Entry& aB) const {
+ return aA.mName == aB.mName;
+ }
+
+ bool LessThan(const Entry& aA, const Entry& aB) const {
+ return aA.mName < aB.mName;
+ }
+ };
+
+ if (!mListDirty) {
+ return;
+ }
+
+ mListDirty = false;
+
+ Comparator comparator;
+
+ mSortedList.Clear();
+ for (const Entry& entry : mList) {
+ bool found = false;
+ for (Entry& sortedEntry : mSortedList) {
+ if (sortedEntry.mName.EqualsIgnoreCase(entry.mName.get())) {
+ sortedEntry.mValue += ", ";
+ sortedEntry.mValue += entry.mValue;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ Entry newEntry = entry;
+ ToLowerCase(newEntry.mName);
+ mSortedList.InsertElementSorted(newEntry, comparator);
+ }
+ }
+}
+
+void InternalHeaders::SetListDirty() {
+ mSortedList.Clear();
+ mListDirty = true;
+}
+
+void InternalHeaders::ReuseExistingNameIfExists(nsCString& aName) const {
+ for (const Entry& entry : mList) {
+ if (entry.mName.EqualsIgnoreCase(aName.get())) {
+ aName = entry.mName;
+ break;
+ }
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/InternalHeaders.h b/dom/fetch/InternalHeaders.h
new file mode 100644
index 0000000000..2fac237733
--- /dev/null
+++ b/dom/fetch/InternalHeaders.h
@@ -0,0 +1,178 @@
+/* -*- 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_InternalHeaders_h
+#define mozilla_dom_InternalHeaders_h
+
+// needed for HeadersGuardEnum.
+#include "mozilla/dom/HeadersBinding.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+
+#include "nsClassHashtable.h"
+#include "nsWrapperCache.h"
+
+class nsIRequest;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+template <typename K, typename V>
+class Record;
+class HeadersEntry;
+
+class InternalHeaders final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalHeaders)
+
+ public:
+ struct Entry {
+ Entry(const nsACString& aName, const nsACString& aValue)
+ : mName(aName), mValue(aValue) {}
+
+ Entry() = default;
+
+ nsCString mName;
+ nsCString mValue;
+ };
+
+ private:
+ HeadersGuardEnum mGuard;
+ nsTArray<Entry> mList;
+
+ nsTArray<Entry> mSortedList;
+
+ // This boolean is set to true at any writing operation to mList. It's set to
+ // false when mSortedList is regenerated. This happens when the header is
+ // iterated.
+ bool mListDirty;
+
+ public:
+ explicit InternalHeaders(HeadersGuardEnum aGuard = HeadersGuardEnum::None)
+ : mGuard(aGuard), mListDirty(false) {}
+
+ explicit InternalHeaders(const InternalHeaders& aOther)
+ : mGuard(HeadersGuardEnum::None), mListDirty(true) {
+ ErrorResult result;
+ Fill(aOther, result);
+ MOZ_ASSERT(!result.Failed());
+ // Note that it's important to set the guard after Fill(), to make sure
+ // that Fill() doesn't fail if aOther is immutable.
+ mGuard = aOther.mGuard;
+ }
+
+ explicit InternalHeaders(nsTArray<Entry>&& aHeaders,
+ HeadersGuardEnum aGuard = HeadersGuardEnum::None);
+
+ InternalHeaders(const nsTArray<HeadersEntry>& aHeadersEntryList,
+ HeadersGuardEnum aGuard);
+
+ void ToIPC(nsTArray<HeadersEntry>& aIPCHeaders, HeadersGuardEnum& aGuard);
+
+ void Append(const nsACString& aName, const nsACString& aValue,
+ ErrorResult& aRv);
+ void Delete(const nsACString& aName, ErrorResult& aRv);
+ void Get(const nsACString& aName, nsACString& aValue, ErrorResult& aRv) const;
+ void GetFirst(const nsACString& aName, nsACString& aValue,
+ ErrorResult& aRv) const;
+ bool Has(const nsACString& aName, ErrorResult& aRv) const;
+ void Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv);
+
+ uint32_t GetIterableLength() {
+ MaybeSortList();
+ return mSortedList.Length();
+ }
+ const NS_ConvertASCIItoUTF16 GetKeyAtIndex(unsigned aIndex) {
+ MaybeSortList();
+ MOZ_ASSERT(aIndex < mSortedList.Length());
+ return NS_ConvertASCIItoUTF16(mSortedList[aIndex].mName);
+ }
+ const NS_ConvertASCIItoUTF16 GetValueAtIndex(unsigned aIndex) {
+ MaybeSortList();
+ MOZ_ASSERT(aIndex < mSortedList.Length());
+ return NS_ConvertASCIItoUTF16(mSortedList[aIndex].mValue);
+ }
+
+ void Clear();
+
+ HeadersGuardEnum Guard() const { return mGuard; }
+ void SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv);
+
+ void Fill(const InternalHeaders& aInit, ErrorResult& aRv);
+ void Fill(const Sequence<Sequence<nsCString>>& aInit, ErrorResult& aRv);
+ void Fill(const Record<nsCString, nsCString>& aInit, ErrorResult& aRv);
+ void FillResponseHeaders(nsIRequest* aRequest);
+
+ bool HasOnlySimpleHeaders() const;
+
+ bool HasRevalidationHeaders() const;
+
+ static already_AddRefed<InternalHeaders> BasicHeaders(
+ InternalHeaders* aHeaders);
+
+ static already_AddRefed<InternalHeaders> CORSHeaders(
+ InternalHeaders* aHeaders,
+ RequestCredentials mCredentialsMode = RequestCredentials::Omit);
+
+ void GetEntries(nsTArray<InternalHeaders::Entry>& aEntries) const;
+
+ void GetUnsafeHeaders(nsTArray<nsCString>& aNames) const;
+
+ private:
+ virtual ~InternalHeaders();
+
+ static bool IsInvalidName(const nsACString& aName, ErrorResult& aRv);
+ static bool IsInvalidValue(const nsACString& aValue, ErrorResult& aRv);
+ bool IsValidHeaderValue(const nsCString& aLowerName,
+ const nsCString& aNormalizedValue, ErrorResult& aRv);
+ bool IsImmutable(ErrorResult& aRv) const;
+ bool IsForbiddenRequestHeader(const nsCString& aName) const;
+ bool IsForbiddenRequestNoCorsHeader(const nsCString& aName) const;
+ bool IsForbiddenRequestNoCorsHeader(const nsCString& aName,
+ const nsACString& aValue) const;
+ bool IsForbiddenResponseHeader(const nsCString& aName) const;
+
+ bool IsInvalidMutableHeader(const nsCString& aName, ErrorResult& aRv) const {
+ return IsInvalidMutableHeader(aName, ""_ns, aRv);
+ }
+
+ bool IsInvalidMutableHeader(const nsCString& aName, const nsACString& aValue,
+ ErrorResult& aRv) const {
+ return IsInvalidName(aName, aRv) || IsInvalidValue(aValue, aRv) ||
+ IsImmutable(aRv) || IsForbiddenRequestHeader(aName) ||
+ IsForbiddenRequestNoCorsHeader(aName, aValue) ||
+ IsForbiddenResponseHeader(aName);
+ }
+
+ // This method updates the passed name to match the capitalization of a header
+ // with the same name (ignoring case, per the spec).
+ void ReuseExistingNameIfExists(nsCString& aName) const;
+
+ void RemovePrivilegedNoCorsRequestHeaders();
+
+ void GetInternal(const nsCString& aLowerName, nsACString& aValue,
+ ErrorResult& aRv) const;
+
+ bool DeleteInternal(const nsCString& aLowerName, ErrorResult& aRv);
+
+ static bool IsNoCorsSafelistedRequestHeaderName(const nsCString& aName);
+
+ static bool IsPrivilegedNoCorsRequestHeaderName(const nsCString& aName);
+
+ static bool IsSimpleHeader(const nsCString& aName, const nsACString& aValue);
+
+ static bool IsRevalidationHeader(const nsCString& aName);
+
+ void MaybeSortList();
+ void SetListDirty();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_InternalHeaders_h
diff --git a/dom/fetch/InternalRequest.cpp b/dom/fetch/InternalRequest.cpp
new file mode 100644
index 0000000000..0f55a8486a
--- /dev/null
+++ b/dom/fetch/InternalRequest.cpp
@@ -0,0 +1,399 @@
+/* -*- 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 "InternalRequest.h"
+
+#include "InternalResponse.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/FetchTypes.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/RemoteLazyInputStreamChild.h"
+#include "nsIContentPolicy.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla::dom {
+// The global is used to extract the principal.
+SafeRefPtr<InternalRequest> InternalRequest::GetRequestConstructorCopy(
+ nsIGlobalObject* aGlobal, ErrorResult& aRv) const {
+ MOZ_RELEASE_ASSERT(!mURLList.IsEmpty(),
+ "Internal Request's urlList should not be empty when "
+ "copied from constructor.");
+ auto copy =
+ MakeSafeRefPtr<InternalRequest>(mURLList.LastElement(), mFragment);
+ copy->SetMethod(mMethod);
+ copy->mHeaders = new InternalHeaders(*mHeaders);
+ copy->SetUnsafeRequest();
+ copy->mBodyStream = mBodyStream;
+ copy->mBodyLength = mBodyLength;
+ // The "client" is not stored in our implementation. Fetch API users should
+ // use the appropriate window/document/principal and other Gecko security
+ // mechanisms as appropriate.
+ copy->mReferrer = mReferrer;
+ copy->mReferrerPolicy = mReferrerPolicy;
+ copy->mEnvironmentReferrerPolicy = mEnvironmentReferrerPolicy;
+ copy->mIntegrity = mIntegrity;
+ copy->mMozErrors = mMozErrors;
+
+ copy->mContentPolicyType = mContentPolicyTypeOverridden
+ ? mContentPolicyType
+ : nsIContentPolicy::TYPE_FETCH;
+ copy->mMode = mMode;
+ copy->mCredentialsMode = mCredentialsMode;
+ copy->mCacheMode = mCacheMode;
+ copy->mRedirectMode = mRedirectMode;
+ copy->mContentPolicyTypeOverridden = mContentPolicyTypeOverridden;
+
+ copy->mPreferredAlternativeDataType = mPreferredAlternativeDataType;
+ return copy;
+}
+
+SafeRefPtr<InternalRequest> InternalRequest::Clone() {
+ auto clone = MakeSafeRefPtr<InternalRequest>(*this, ConstructorGuard{});
+
+ if (!mBodyStream) {
+ return clone;
+ }
+
+ nsCOMPtr<nsIInputStream> clonedBody;
+ nsCOMPtr<nsIInputStream> replacementBody;
+
+ nsresult rv = NS_CloneInputStream(mBodyStream, getter_AddRefs(clonedBody),
+ getter_AddRefs(replacementBody));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ clone->mBodyStream.swap(clonedBody);
+ if (replacementBody) {
+ mBodyStream.swap(replacementBody);
+ }
+ return clone;
+}
+InternalRequest::InternalRequest(const nsACString& aURL,
+ const nsACString& aFragment)
+ : mMethod("GET"),
+ mHeaders(new InternalHeaders(HeadersGuardEnum::None)),
+ mBodyLength(InternalResponse::UNKNOWN_BODY_SIZE),
+ mContentPolicyType(nsIContentPolicy::TYPE_FETCH),
+ mReferrer(NS_LITERAL_STRING_FROM_CSTRING(kFETCH_CLIENT_REFERRER_STR)),
+ mReferrerPolicy(ReferrerPolicy::_empty),
+ mEnvironmentReferrerPolicy(ReferrerPolicy::_empty),
+ mMode(RequestMode::No_cors),
+ mCredentialsMode(RequestCredentials::Omit),
+ mCacheMode(RequestCache::Default),
+ mRedirectMode(RequestRedirect::Follow) {
+ MOZ_ASSERT(!aURL.IsEmpty());
+ AddURL(aURL, aFragment);
+}
+InternalRequest::InternalRequest(
+ const nsACString& aURL, const nsACString& aFragment,
+ const nsACString& aMethod, already_AddRefed<InternalHeaders> aHeaders,
+ RequestCache aCacheMode, RequestMode aMode,
+ RequestRedirect aRequestRedirect, RequestCredentials aRequestCredentials,
+ const nsAString& aReferrer, ReferrerPolicy aReferrerPolicy,
+ nsContentPolicyType aContentPolicyType, const nsAString& aIntegrity)
+ : mMethod(aMethod),
+ mHeaders(aHeaders),
+ mBodyLength(InternalResponse::UNKNOWN_BODY_SIZE),
+ mContentPolicyType(aContentPolicyType),
+ mReferrer(aReferrer),
+ mReferrerPolicy(aReferrerPolicy),
+ mEnvironmentReferrerPolicy(ReferrerPolicy::_empty),
+ mMode(aMode),
+ mCredentialsMode(aRequestCredentials),
+ mCacheMode(aCacheMode),
+ mRedirectMode(aRequestRedirect),
+ mIntegrity(aIntegrity) {
+ MOZ_ASSERT(!aURL.IsEmpty());
+ AddURL(aURL, aFragment);
+}
+InternalRequest::InternalRequest(const InternalRequest& aOther,
+ ConstructorGuard)
+ : mMethod(aOther.mMethod),
+ mURLList(aOther.mURLList.Clone()),
+ mHeaders(new InternalHeaders(*aOther.mHeaders)),
+ mBodyLength(InternalResponse::UNKNOWN_BODY_SIZE),
+ mContentPolicyType(aOther.mContentPolicyType),
+ mReferrer(aOther.mReferrer),
+ mReferrerPolicy(aOther.mReferrerPolicy),
+ mEnvironmentReferrerPolicy(aOther.mEnvironmentReferrerPolicy),
+ mMode(aOther.mMode),
+ mCredentialsMode(aOther.mCredentialsMode),
+ mResponseTainting(aOther.mResponseTainting),
+ mCacheMode(aOther.mCacheMode),
+ mRedirectMode(aOther.mRedirectMode),
+ mIntegrity(aOther.mIntegrity),
+ mMozErrors(aOther.mMozErrors),
+ mFragment(aOther.mFragment),
+ mSkipServiceWorker(aOther.mSkipServiceWorker),
+ mSynchronous(aOther.mSynchronous),
+ mUnsafeRequest(aOther.mUnsafeRequest),
+ mUseURLCredentials(aOther.mUseURLCredentials),
+ mContentPolicyTypeOverridden(aOther.mContentPolicyTypeOverridden) {
+ // NOTE: does not copy body stream... use the fallible Clone() for that
+}
+
+InternalRequest::InternalRequest(const IPCInternalRequest& aIPCRequest)
+ : mMethod(aIPCRequest.method()),
+ mURLList(aIPCRequest.urlList().Clone()),
+ mHeaders(new InternalHeaders(aIPCRequest.headers(),
+ aIPCRequest.headersGuard())),
+ mBodyLength(aIPCRequest.bodySize()),
+ mPreferredAlternativeDataType(aIPCRequest.preferredAlternativeDataType()),
+ mContentPolicyType(
+ static_cast<nsContentPolicyType>(aIPCRequest.contentPolicyType())),
+ mReferrer(aIPCRequest.referrer()),
+ mReferrerPolicy(aIPCRequest.referrerPolicy()),
+ mMode(aIPCRequest.requestMode()),
+ mCredentialsMode(aIPCRequest.requestCredentials()),
+ mCacheMode(aIPCRequest.cacheMode()),
+ mRedirectMode(aIPCRequest.requestRedirect()),
+ mIntegrity(aIPCRequest.integrity()),
+ mFragment(aIPCRequest.fragment()) {
+ if (aIPCRequest.principalInfo()) {
+ mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(
+ aIPCRequest.principalInfo().ref());
+ }
+
+ const Maybe<BodyStreamVariant>& body = aIPCRequest.body();
+
+ // This constructor is (currently) only used for parent -> child communication
+ // (constructed on the child side).
+ if (body) {
+ MOZ_ASSERT(body->type() == BodyStreamVariant::TParentToChildStream);
+ mBodyStream = static_cast<RemoteLazyInputStreamChild*>(
+ body->get_ParentToChildStream().actorChild())
+ ->CreateStream();
+ }
+}
+
+InternalRequest::~InternalRequest() = default;
+
+void InternalRequest::SetContentPolicyType(
+ nsContentPolicyType aContentPolicyType) {
+ mContentPolicyType = aContentPolicyType;
+}
+
+void InternalRequest::OverrideContentPolicyType(
+ nsContentPolicyType aContentPolicyType) {
+ SetContentPolicyType(aContentPolicyType);
+ mContentPolicyTypeOverridden = true;
+}
+
+/* static */
+RequestDestination InternalRequest::MapContentPolicyTypeToRequestDestination(
+ nsContentPolicyType aContentPolicyType) {
+ switch (aContentPolicyType) {
+ case nsIContentPolicy::TYPE_OTHER:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
+ case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
+ case nsIContentPolicy::TYPE_SCRIPT:
+ return RequestDestination::Script;
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER:
+ return RequestDestination::Worker;
+ case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
+ return RequestDestination::Sharedworker;
+ case nsIContentPolicy::TYPE_IMAGESET:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
+ case nsIContentPolicy::TYPE_IMAGE:
+ return RequestDestination::Image;
+ case nsIContentPolicy::TYPE_STYLESHEET:
+ case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
+ case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
+ return RequestDestination::Style;
+ case nsIContentPolicy::TYPE_OBJECT:
+ case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
+ return RequestDestination::Object;
+ case nsIContentPolicy::TYPE_INTERNAL_EMBED:
+ return RequestDestination::Embed;
+ case nsIContentPolicy::TYPE_DOCUMENT:
+ return RequestDestination::Document;
+ case nsIContentPolicy::TYPE_SUBDOCUMENT:
+ case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
+ return RequestDestination::Iframe;
+ case nsIContentPolicy::TYPE_INTERNAL_FRAME:
+ return RequestDestination::Frame;
+ case nsIContentPolicy::TYPE_PING:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
+ case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_DTD:
+ case nsIContentPolicy::TYPE_INTERNAL_DTD:
+ case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_FONT:
+ case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD:
+ return RequestDestination::Font;
+ case nsIContentPolicy::TYPE_MEDIA:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
+ return RequestDestination::Audio;
+ case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
+ return RequestDestination::Video;
+ case nsIContentPolicy::TYPE_INTERNAL_TRACK:
+ return RequestDestination::Track;
+ case nsIContentPolicy::TYPE_WEBSOCKET:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_CSP_REPORT:
+ return RequestDestination::Report;
+ case nsIContentPolicy::TYPE_XSLT:
+ return RequestDestination::Xslt;
+ case nsIContentPolicy::TYPE_BEACON:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_FETCH:
+ case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_WEB_MANIFEST:
+ return RequestDestination::Manifest;
+ case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_SPECULATIVE:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
+ return RequestDestination::Audioworklet;
+ case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
+ return RequestDestination::Paintworklet;
+ case nsIContentPolicy::TYPE_INVALID:
+ break;
+ // Do not add default: so that compilers can catch the missing case.
+ }
+
+ MOZ_ASSERT(false, "Unhandled nsContentPolicyType value");
+ return RequestDestination::_empty;
+}
+
+// static
+bool InternalRequest::IsNavigationContentPolicy(
+ nsContentPolicyType aContentPolicyType) {
+ // https://fetch.spec.whatwg.org/#navigation-request-context
+ //
+ // A navigation request context is one of "form", "frame", "hyperlink",
+ // "iframe", "internal" (as long as context frame type is not "none"),
+ // "location", "metarefresh", and "prerender".
+ //
+ // Note, all of these request types are effectively initiated by nsDocShell.
+ return aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
+ aContentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT ||
+ aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME ||
+ aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME;
+}
+
+// static
+bool InternalRequest::IsWorkerContentPolicy(
+ nsContentPolicyType aContentPolicyType) {
+ // https://fetch.spec.whatwg.org/#worker-request-context
+ //
+ // A worker request context is one of "serviceworker", "sharedworker", and
+ // "worker".
+ //
+ // Note, service workers are not included here because currently there is
+ // no way to generate a Request with a "serviceworker" RequestDestination.
+ // ServiceWorker scripts cannot be intercepted.
+ return aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER;
+}
+
+bool InternalRequest::IsNavigationRequest() const {
+ return IsNavigationContentPolicy(mContentPolicyType);
+}
+
+bool InternalRequest::IsWorkerRequest() const {
+ return IsWorkerContentPolicy(mContentPolicyType);
+}
+
+bool InternalRequest::IsClientRequest() const {
+ return IsNavigationRequest() || IsWorkerRequest();
+}
+
+// static
+RequestMode InternalRequest::MapChannelToRequestMode(nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ nsContentPolicyType contentPolicy = loadInfo->InternalContentPolicyType();
+ if (IsNavigationContentPolicy(contentPolicy)) {
+ return RequestMode::Navigate;
+ }
+
+ // TODO: remove the worker override once securityMode is fully implemented
+ // (bug 1189945)
+ if (IsWorkerContentPolicy(contentPolicy)) {
+ return RequestMode::Same_origin;
+ }
+
+ uint32_t securityMode = loadInfo->GetSecurityMode();
+
+ switch (securityMode) {
+ case nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT:
+ case nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED:
+ return RequestMode::Same_origin;
+ case nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT:
+ case nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL:
+ return RequestMode::No_cors;
+ case nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT:
+ // TODO: Check additional flag force-preflight after bug 1199693 (bug
+ // 1189945)
+ return RequestMode::Cors;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected security mode!");
+ return RequestMode::Same_origin;
+ }
+}
+
+// static
+RequestCredentials InternalRequest::MapChannelToRequestCredentials(
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ uint32_t cookiePolicy = loadInfo->GetCookiePolicy();
+
+ if (cookiePolicy == nsILoadInfo::SEC_COOKIES_INCLUDE) {
+ return RequestCredentials::Include;
+ } else if (cookiePolicy == nsILoadInfo::SEC_COOKIES_OMIT) {
+ return RequestCredentials::Omit;
+ } else if (cookiePolicy == nsILoadInfo::SEC_COOKIES_SAME_ORIGIN) {
+ return RequestCredentials::Same_origin;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unexpected cookie policy!");
+ return RequestCredentials::Same_origin;
+}
+
+void InternalRequest::MaybeSkipCacheIfPerformingRevalidation() {
+ if (mCacheMode == RequestCache::Default &&
+ mHeaders->HasRevalidationHeaders()) {
+ mCacheMode = RequestCache::No_store;
+ }
+}
+
+void InternalRequest::SetPrincipalInfo(
+ UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo) {
+ mPrincipalInfo = std::move(aPrincipalInfo);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/InternalRequest.h b/dom/fetch/InternalRequest.h
new file mode 100644
index 0000000000..a526089fe2
--- /dev/null
+++ b/dom/fetch/InternalRequest.h
@@ -0,0 +1,428 @@
+/* -*- 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_InternalRequest_h
+#define mozilla_dom_InternalRequest_h
+
+#include "mozilla/dom/HeadersBinding.h"
+#include "mozilla/dom/InternalHeaders.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/SafeRefPtr.h"
+#include "mozilla/LoadTainting.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsIChannelEventSink.h"
+#include "nsIInputStream.h"
+#include "nsISupportsImpl.h"
+#ifdef DEBUG
+# include "nsIURLParser.h"
+# include "nsNetCID.h"
+# include "nsServiceManagerUtils.h"
+#endif
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+/*
+ * The mapping of RequestDestination and nsContentPolicyType is currently as the
+ * following.
+ *
+ * RequestDestination| nsContentPolicyType
+ * ------------------+--------------------
+ * "audio" | TYPE_INTERNAL_AUDIO
+ * "audioworklet" | TYPE_INTERNAL_AUDIOWORKLET
+ * "document" | TYPE_DOCUMENT
+ * "embed" | TYPE_INTERNAL_EMBED
+ * "font" | TYPE_FONT, TYPE_INTERNAL_FONT_PRELOAD
+ * "frame" | TYPE_INTERNAL_FRAME
+ * "iframe" | TYPE_SUBDOCUMENT, TYPE_INTERNAL_IFRAME
+ * "image" | TYPE_INTERNAL_IMAGE, TYPE_INTERNAL_IMAGE_PRELOAD,
+ * | TYPE_IMAGE, TYPE_INTERNAL_IMAGE_FAVICON, TYPE_IMAGESET
+ * "manifest" | TYPE_WEB_MANIFEST
+ * "object" | TYPE_INTERNAL_OBJECT, TYPE_OBJECT
+ * "paintworklet" | TYPE_INTERNAL_PAINTWORKLET
+ * "report" | TYPE_CSP_REPORT
+ * "script" | TYPE_INTERNAL_SCRIPT, TYPE_INTERNAL_SCRIPT_PRELOAD,
+ * | TYPE_INTERNAL_MODULE, TYPE_INTERNAL_MODULE_PRELOAD,
+ * | TYPE_SCRIPT,
+ * | TYPE_INTERNAL_SERVICE_WORKER,
+ * | TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS,
+ * | TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT
+ * | TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT
+ * "sharedworker" | TYPE_INTERNAL_SHARED_WORKER
+ * "serviceworker" | The spec lists this as a valid value for the enum,
+ * | however it is impossible to observe a request with this
+ * | destination value.
+ * "style" | TYPE_INTERNAL_STYLESHEET,
+ * | TYPE_INTERNAL_STYLESHEET_PRELOAD,
+ * | TYPE_STYLESHEET
+ * "track" | TYPE_INTERNAL_TRACK
+ * "video" | TYPE_INTERNAL_VIDEO
+ * "worker" | TYPE_INTERNAL_WORKER
+ * "xslt" | TYPE_XSLT
+ * "" | Default for everything else.
+ *
+ */
+
+class IPCInternalRequest;
+class Request;
+
+#define kFETCH_CLIENT_REFERRER_STR "about:client"
+class InternalRequest final : public AtomicSafeRefCounted<InternalRequest> {
+ friend class Request;
+
+ public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(InternalRequest)
+ InternalRequest(const nsACString& aURL, const nsACString& aFragment);
+ InternalRequest(const nsACString& aURL, const nsACString& aFragment,
+ const nsACString& aMethod,
+ already_AddRefed<InternalHeaders> aHeaders,
+ RequestCache aCacheMode, RequestMode aMode,
+ RequestRedirect aRequestRedirect,
+ RequestCredentials aRequestCredentials,
+ const nsAString& aReferrer, ReferrerPolicy aReferrerPolicy,
+ nsContentPolicyType aContentPolicyType,
+ const nsAString& aIntegrity);
+
+ explicit InternalRequest(const IPCInternalRequest& aIPCRequest);
+
+ SafeRefPtr<InternalRequest> Clone();
+
+ void GetMethod(nsCString& aMethod) const { aMethod.Assign(mMethod); }
+
+ void SetMethod(const nsACString& aMethod) { mMethod.Assign(aMethod); }
+
+ bool HasSimpleMethod() const {
+ return mMethod.LowerCaseEqualsASCII("get") ||
+ mMethod.LowerCaseEqualsASCII("post") ||
+ mMethod.LowerCaseEqualsASCII("head");
+ }
+ // GetURL should get the request's current url with fragment. A request has
+ // an associated current url. It is a pointer to the last fetch URL in
+ // request's url list.
+ void GetURL(nsACString& aURL) const {
+ aURL.Assign(GetURLWithoutFragment());
+ if (GetFragment().IsEmpty()) {
+ return;
+ }
+ aURL.AppendLiteral("#");
+ aURL.Append(GetFragment());
+ }
+
+ const nsCString& GetURLWithoutFragment() const {
+ MOZ_RELEASE_ASSERT(!mURLList.IsEmpty(),
+ "Internal Request's urlList should not be empty.");
+
+ return mURLList.LastElement();
+ }
+
+ // A safe guard for ensuring that request's URL is only allowed to be set in a
+ // sw internal redirect.
+ void SetURLForInternalRedirect(const uint32_t aFlag, const nsACString& aURL,
+ const nsACString& aFragment) {
+ // Only check in debug build to prevent it from being used unexpectedly.
+ MOZ_ASSERT(aFlag & nsIChannelEventSink::REDIRECT_INTERNAL);
+
+ return SetURL(aURL, aFragment);
+ }
+
+ // AddURL should append the url into url list.
+ // Normally we strip the fragment from the URL in Request::Constructor and
+ // pass the fragment as the second argument into it.
+ // If a fragment is present in the URL it must be stripped and passed in
+ // separately.
+ void AddURL(const nsACString& aURL, const nsACString& aFragment) {
+ MOZ_ASSERT(!aURL.IsEmpty());
+ MOZ_ASSERT(!aURL.Contains('#'));
+
+ mURLList.AppendElement(aURL);
+
+ mFragment.Assign(aFragment);
+ }
+ // Get the URL list without their fragments.
+ void GetURLListWithoutFragment(nsTArray<nsCString>& aURLList) {
+ aURLList.Assign(mURLList);
+ }
+ void GetReferrer(nsAString& aReferrer) const { aReferrer.Assign(mReferrer); }
+
+ void SetReferrer(const nsAString& aReferrer) {
+#ifdef DEBUG
+ bool validReferrer = false;
+ if (aReferrer.IsEmpty() ||
+ aReferrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
+ validReferrer = true;
+ } else {
+ nsCOMPtr<nsIURLParser> parser = do_GetService(NS_STDURLPARSER_CONTRACTID);
+ if (!parser) {
+ NS_WARNING("Could not get parser to validate URL!");
+ } else {
+ uint32_t schemePos;
+ int32_t schemeLen;
+ uint32_t authorityPos;
+ int32_t authorityLen;
+ uint32_t pathPos;
+ int32_t pathLen;
+
+ NS_ConvertUTF16toUTF8 ref(aReferrer);
+ nsresult rv =
+ parser->ParseURL(ref.get(), ref.Length(), &schemePos, &schemeLen,
+ &authorityPos, &authorityLen, &pathPos, &pathLen);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Invalid referrer URL!");
+ } else if (schemeLen < 0 || authorityLen < 0) {
+ NS_WARNING("Invalid referrer URL!");
+ } else {
+ validReferrer = true;
+ }
+ }
+ }
+
+ MOZ_ASSERT(validReferrer);
+#endif
+
+ mReferrer.Assign(aReferrer);
+ }
+
+ ReferrerPolicy ReferrerPolicy_() const { return mReferrerPolicy; }
+
+ void SetReferrerPolicy(ReferrerPolicy aReferrerPolicy) {
+ mReferrerPolicy = aReferrerPolicy;
+ }
+
+ ReferrerPolicy GetEnvironmentReferrerPolicy() const {
+ return mEnvironmentReferrerPolicy;
+ }
+
+ void SetEnvironmentReferrerPolicy(ReferrerPolicy aReferrerPolicy) {
+ mEnvironmentReferrerPolicy = aReferrerPolicy;
+ }
+
+ bool SkipServiceWorker() const { return mSkipServiceWorker; }
+
+ void SetSkipServiceWorker() { mSkipServiceWorker = true; }
+
+ bool IsSynchronous() const { return mSynchronous; }
+
+ RequestMode Mode() const { return mMode; }
+
+ void SetMode(RequestMode aMode) { mMode = aMode; }
+
+ RequestCredentials GetCredentialsMode() const { return mCredentialsMode; }
+
+ void SetCredentialsMode(RequestCredentials aCredentialsMode) {
+ mCredentialsMode = aCredentialsMode;
+ }
+
+ LoadTainting GetResponseTainting() const { return mResponseTainting; }
+
+ void MaybeIncreaseResponseTainting(LoadTainting aTainting) {
+ if (aTainting > mResponseTainting) {
+ mResponseTainting = aTainting;
+ }
+ }
+
+ RequestCache GetCacheMode() const { return mCacheMode; }
+
+ void SetCacheMode(RequestCache aCacheMode) { mCacheMode = aCacheMode; }
+
+ RequestRedirect GetRedirectMode() const { return mRedirectMode; }
+
+ void SetRedirectMode(RequestRedirect aRedirectMode) {
+ mRedirectMode = aRedirectMode;
+ }
+
+ const nsString& GetIntegrity() const { return mIntegrity; }
+
+ void SetIntegrity(const nsAString& aIntegrity) {
+ mIntegrity.Assign(aIntegrity);
+ }
+
+ bool MozErrors() const { return mMozErrors; }
+
+ void SetMozErrors() { mMozErrors = true; }
+
+ const nsCString& GetFragment() const { return mFragment; }
+
+ nsContentPolicyType ContentPolicyType() const { return mContentPolicyType; }
+ void SetContentPolicyType(nsContentPolicyType aContentPolicyType);
+
+ void OverrideContentPolicyType(nsContentPolicyType aContentPolicyType);
+
+ RequestDestination Destination() const {
+ return MapContentPolicyTypeToRequestDestination(mContentPolicyType);
+ }
+
+ bool UnsafeRequest() const { return mUnsafeRequest; }
+
+ void SetUnsafeRequest() { mUnsafeRequest = true; }
+
+ InternalHeaders* Headers() const { return mHeaders; }
+
+ void SetHeaders(InternalHeaders* aHeaders) {
+ MOZ_ASSERT(aHeaders);
+ mHeaders = aHeaders;
+ }
+
+ void SetBody(nsIInputStream* aStream, int64_t aBodyLength) {
+ // A request's body may not be reset once set.
+ MOZ_ASSERT_IF(aStream, !mBodyStream);
+ mBodyStream = aStream;
+ mBodyLength = aBodyLength;
+ }
+
+ // Will return the original stream!
+ // Use a tee or copy if you don't want to erase the original.
+ void GetBody(nsIInputStream** aStream, int64_t* aBodyLength = nullptr) const {
+ nsCOMPtr<nsIInputStream> s = mBodyStream;
+ s.forget(aStream);
+
+ if (aBodyLength) {
+ *aBodyLength = mBodyLength;
+ }
+ }
+
+ void SetBodyBlobURISpec(nsACString& aBlobURISpec) {
+ mBodyBlobURISpec = aBlobURISpec;
+ }
+
+ const nsACString& BodyBlobURISpec() const { return mBodyBlobURISpec; }
+
+ void SetBodyLocalPath(nsAString& aLocalPath) { mBodyLocalPath = aLocalPath; }
+
+ const nsAString& BodyLocalPath() const { return mBodyLocalPath; }
+
+ // The global is used as the client for the new object.
+ SafeRefPtr<InternalRequest> GetRequestConstructorCopy(
+ nsIGlobalObject* aGlobal, ErrorResult& aRv) const;
+
+ bool IsNavigationRequest() const;
+
+ bool IsWorkerRequest() const;
+
+ bool IsClientRequest() const;
+
+ void MaybeSkipCacheIfPerformingRevalidation();
+
+ bool IsContentPolicyTypeOverridden() const {
+ return mContentPolicyTypeOverridden;
+ }
+
+ static RequestMode MapChannelToRequestMode(nsIChannel* aChannel);
+
+ static RequestCredentials MapChannelToRequestCredentials(
+ nsIChannel* aChannel);
+
+ // Takes ownership of the principal info.
+ void SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo);
+
+ const UniquePtr<mozilla::ipc::PrincipalInfo>& GetPrincipalInfo() const {
+ return mPrincipalInfo;
+ }
+
+ const nsCString& GetPreferredAlternativeDataType() const {
+ return mPreferredAlternativeDataType;
+ }
+
+ void SetPreferredAlternativeDataType(const nsACString& aDataType) {
+ mPreferredAlternativeDataType = aDataType;
+ }
+
+ ~InternalRequest();
+
+ InternalRequest(const InternalRequest& aOther) = delete;
+
+ void SetEmbedderPolicy(nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) {
+ mEmbedderPolicy = aPolicy;
+ }
+
+ nsILoadInfo::CrossOriginEmbedderPolicy GetEmbedderPolicy() const {
+ return mEmbedderPolicy;
+ }
+
+ private:
+ struct ConstructorGuard {};
+
+ public:
+ // Does not copy mBodyStream. Use fallible Clone() for complete copy.
+ InternalRequest(const InternalRequest& aOther, ConstructorGuard);
+
+ private:
+ // Map the content policy type to the associated fetch destination, as defined
+ // by the spec at https://fetch.spec.whatwg.org/#concept-request-destination.
+ // Note that while the HTML spec for the "Link" element and its "as" attribute
+ // (https://html.spec.whatwg.org/#attr-link-as) reuse fetch's definition of
+ // destination.
+ static RequestDestination MapContentPolicyTypeToRequestDestination(
+ nsContentPolicyType aContentPolicyType);
+
+ static bool IsNavigationContentPolicy(nsContentPolicyType aContentPolicyType);
+
+ static bool IsWorkerContentPolicy(nsContentPolicyType aContentPolicyType);
+
+ // It should only be called while there is a service-worker-internal-redirect.
+ void SetURL(const nsACString& aURL, const nsACString& aFragment) {
+ MOZ_ASSERT(!aURL.IsEmpty());
+ MOZ_ASSERT(!aURL.Contains('#'));
+ MOZ_ASSERT(mURLList.Length() > 0);
+
+ mURLList.LastElement() = aURL;
+ mFragment.Assign(aFragment);
+ }
+
+ nsCString mMethod;
+ // mURLList: a list of one or more fetch URLs
+ nsTArray<nsCString> mURLList;
+ RefPtr<InternalHeaders> mHeaders;
+ nsCString mBodyBlobURISpec;
+ nsString mBodyLocalPath;
+ nsCOMPtr<nsIInputStream> mBodyStream;
+ int64_t mBodyLength;
+
+ nsCString mPreferredAlternativeDataType;
+
+ nsContentPolicyType mContentPolicyType;
+
+ // Empty string: no-referrer
+ // "about:client": client (default)
+ // URL: an URL
+ nsString mReferrer;
+ ReferrerPolicy mReferrerPolicy;
+
+ // This will be used for request created from Window or Worker contexts
+ // In case there's no Referrer Policy in Request, this will be passed to
+ // channel.
+ ReferrerPolicy mEnvironmentReferrerPolicy;
+ RequestMode mMode;
+ RequestCredentials mCredentialsMode;
+ LoadTainting mResponseTainting = LoadTainting::Basic;
+ RequestCache mCacheMode;
+ RequestRedirect mRedirectMode;
+ nsString mIntegrity;
+ bool mMozErrors = false;
+ nsCString mFragment;
+ bool mSkipServiceWorker = false;
+ bool mSynchronous = false;
+ bool mUnsafeRequest = false;
+ bool mUseURLCredentials = false;
+ // This is only set when Request.overrideContentPolicyType() has been set.
+ // It is illegal to pass such a Request object to a fetch() method unless
+ // if the caller has chrome privileges.
+ bool mContentPolicyTypeOverridden = false;
+ nsILoadInfo::CrossOriginEmbedderPolicy mEmbedderPolicy =
+ nsILoadInfo::EMBEDDER_POLICY_NULL;
+
+ UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_InternalRequest_h
diff --git a/dom/fetch/InternalResponse.cpp b/dom/fetch/InternalResponse.cpp
new file mode 100644
index 0000000000..342a88eeeb
--- /dev/null
+++ b/dom/fetch/InternalResponse.cpp
@@ -0,0 +1,361 @@
+/* -*- 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 "InternalResponse.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/FetchTypes.h"
+#include "mozilla/dom/InternalHeaders.h"
+#include "mozilla/dom/cache/CacheTypes.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/RandomNum.h"
+#include "mozilla/RemoteLazyInputStreamStorage.h"
+#include "nsIRandomGenerator.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+// Const variable for generate padding size
+// XXX This will be tweaked to something more meaningful in Bug 1383656.
+const uint32_t kMaxRandomNumber = 102400;
+
+nsCOMPtr<nsIInputStream> TakeStreamFromStorage(
+ const BodyStreamVariant& aVariant, int64_t aBodySize) {
+ MOZ_ASSERT(aVariant.type() == BodyStreamVariant::TParentToParentStream);
+ const auto& uuid = aVariant.get_ParentToParentStream().uuid();
+
+ auto storageOrErr = RemoteLazyInputStreamStorage::Get();
+ MOZ_ASSERT(storageOrErr.isOk());
+ auto storage = storageOrErr.unwrap();
+ auto stream = storage->ForgetStream(uuid);
+ MOZ_ASSERT(stream);
+
+ return stream;
+}
+
+} // namespace
+
+InternalResponse::InternalResponse(uint16_t aStatus,
+ const nsACString& aStatusText,
+ RequestCredentials aCredentialsMode)
+ : mType(ResponseType::Default),
+ mStatus(aStatus),
+ mStatusText(aStatusText),
+ mHeaders(new InternalHeaders(HeadersGuardEnum::Response)),
+ mBodySize(UNKNOWN_BODY_SIZE),
+ mPaddingSize(UNKNOWN_PADDING_SIZE),
+ mErrorCode(NS_OK),
+ mCredentialsMode(aCredentialsMode) {}
+
+/* static */ RefPtr<InternalResponse> InternalResponse::FromIPC(
+ const IPCInternalResponse& aIPCResponse) {
+ if (aIPCResponse.type() == ResponseType::Error) {
+ return InternalResponse::NetworkError(aIPCResponse.errorCode());
+ }
+
+ RefPtr<InternalResponse> response =
+ new InternalResponse(aIPCResponse.status(), aIPCResponse.statusText());
+
+ response->SetURLList(aIPCResponse.urlList());
+ response->mHeaders =
+ new InternalHeaders(aIPCResponse.headers(), aIPCResponse.headersGuard());
+
+ if (aIPCResponse.body()) {
+ auto bodySize = aIPCResponse.bodySize();
+ nsCOMPtr<nsIInputStream> body =
+ TakeStreamFromStorage(*aIPCResponse.body(), bodySize);
+ response->SetBody(body, bodySize);
+ }
+
+ response->SetAlternativeDataType(aIPCResponse.alternativeDataType());
+
+ if (aIPCResponse.alternativeBody()) {
+ nsCOMPtr<nsIInputStream> alternativeBody = TakeStreamFromStorage(
+ *aIPCResponse.alternativeBody(), UNKNOWN_BODY_SIZE);
+ response->SetAlternativeBody(alternativeBody);
+ }
+
+ response->InitChannelInfo(aIPCResponse.channelInfo());
+
+ if (aIPCResponse.principalInfo()) {
+ response->SetPrincipalInfo(MakeUnique<mozilla::ipc::PrincipalInfo>(
+ aIPCResponse.principalInfo().ref()));
+ }
+
+ switch (aIPCResponse.type()) {
+ case ResponseType::Basic:
+ response = response->BasicResponse();
+ break;
+ case ResponseType::Cors:
+ response = response->CORSResponse();
+ break;
+ case ResponseType::Default:
+ break;
+ case ResponseType::Opaque:
+ response = response->OpaqueResponse();
+ break;
+ case ResponseType::Opaqueredirect:
+ response = response->OpaqueRedirectResponse();
+ break;
+ default:
+ MOZ_CRASH("Unexpected ResponseType!");
+ }
+
+ MOZ_ASSERT(response);
+
+ return response;
+}
+
+InternalResponse::~InternalResponse() = default;
+
+void InternalResponse::ToIPC(
+ IPCInternalResponse* aIPCResponse, mozilla::ipc::PBackgroundChild* aManager,
+ UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoBodyStream,
+ UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoAlternativeBodyStream) {
+ nsTArray<HeadersEntry> headers;
+ HeadersGuardEnum headersGuard;
+ UnfilteredHeaders()->ToIPC(headers, headersGuard);
+
+ Maybe<mozilla::ipc::PrincipalInfo> principalInfo =
+ mPrincipalInfo ? Some(*mPrincipalInfo) : Nothing();
+
+ // Note: all the arguments are copied rather than moved, which would be more
+ // efficient, because there's no move-friendly constructor generated.
+ *aIPCResponse =
+ IPCInternalResponse(mType, GetUnfilteredURLList(), GetUnfilteredStatus(),
+ GetUnfilteredStatusText(), headersGuard, headers,
+ Nothing(), static_cast<uint64_t>(UNKNOWN_BODY_SIZE),
+ mErrorCode, GetAlternativeDataType(), Nothing(),
+ mChannelInfo.AsIPCChannelInfo(), principalInfo);
+
+ nsCOMPtr<nsIInputStream> body;
+ int64_t bodySize;
+ GetUnfilteredBody(getter_AddRefs(body), &bodySize);
+
+ if (body) {
+ aIPCResponse->body().emplace(ChildToParentStream());
+ aIPCResponse->bodySize() = bodySize;
+
+ aAutoBodyStream.reset(new mozilla::ipc::AutoIPCStream(
+ aIPCResponse->body()->get_ChildToParentStream().stream()));
+ DebugOnly<bool> ok = aAutoBodyStream->Serialize(body, aManager);
+ MOZ_ASSERT(ok);
+ }
+
+ nsCOMPtr<nsIInputStream> alternativeBody = TakeAlternativeBody();
+ if (alternativeBody) {
+ aIPCResponse->alternativeBody().emplace(ChildToParentStream());
+
+ aAutoAlternativeBodyStream.reset(new mozilla::ipc::AutoIPCStream(
+ aIPCResponse->alternativeBody()->get_ChildToParentStream().stream()));
+ DebugOnly<bool> ok =
+ aAutoAlternativeBodyStream->Serialize(alternativeBody, aManager);
+ MOZ_ASSERT(ok);
+ }
+}
+
+already_AddRefed<InternalResponse> InternalResponse::Clone(
+ CloneType aCloneType) {
+ RefPtr<InternalResponse> clone = CreateIncompleteCopy();
+
+ clone->mHeaders = new InternalHeaders(*mHeaders);
+
+ // Make sure the clone response will have the same padding size.
+ clone->mPaddingInfo = mPaddingInfo;
+ clone->mPaddingSize = mPaddingSize;
+
+ clone->mCacheInfoChannel = mCacheInfoChannel;
+
+ if (mWrappedResponse) {
+ clone->mWrappedResponse = mWrappedResponse->Clone(aCloneType);
+ MOZ_ASSERT(!mBody);
+ return clone.forget();
+ }
+
+ if (!mBody || aCloneType == eDontCloneInputStream) {
+ return clone.forget();
+ }
+
+ nsCOMPtr<nsIInputStream> clonedBody;
+ nsCOMPtr<nsIInputStream> replacementBody;
+
+ nsresult rv = NS_CloneInputStream(mBody, getter_AddRefs(clonedBody),
+ getter_AddRefs(replacementBody));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ clone->mBody.swap(clonedBody);
+ if (replacementBody) {
+ mBody.swap(replacementBody);
+ }
+
+ return clone.forget();
+}
+
+already_AddRefed<InternalResponse> InternalResponse::BasicResponse() {
+ MOZ_ASSERT(!mWrappedResponse,
+ "Can't BasicResponse a already wrapped response");
+ RefPtr<InternalResponse> basic = CreateIncompleteCopy();
+ basic->mType = ResponseType::Basic;
+ basic->mHeaders = InternalHeaders::BasicHeaders(Headers());
+ basic->mWrappedResponse = this;
+ return basic.forget();
+}
+
+already_AddRefed<InternalResponse> InternalResponse::CORSResponse() {
+ MOZ_ASSERT(!mWrappedResponse,
+ "Can't CORSResponse a already wrapped response");
+ RefPtr<InternalResponse> cors = CreateIncompleteCopy();
+ cors->mType = ResponseType::Cors;
+ cors->mHeaders = InternalHeaders::CORSHeaders(Headers(), mCredentialsMode);
+ cors->mWrappedResponse = this;
+ return cors.forget();
+}
+
+uint32_t InternalResponse::GetPaddingInfo() {
+ // If it's an opaque response, the paddingInfo should be generated only when
+ // paddingSize is unknown size.
+ // If it's not, the paddingInfo should be nothing and the paddingSize should
+ // be unknown size.
+ MOZ_DIAGNOSTIC_ASSERT(
+ (mType == ResponseType::Opaque && mPaddingSize == UNKNOWN_PADDING_SIZE &&
+ mPaddingInfo.isSome()) ||
+ (mType == ResponseType::Opaque && mPaddingSize != UNKNOWN_PADDING_SIZE &&
+ mPaddingInfo.isNothing()) ||
+ (mType != ResponseType::Opaque && mPaddingSize == UNKNOWN_PADDING_SIZE &&
+ mPaddingInfo.isNothing()));
+ return mPaddingInfo.isSome() ? mPaddingInfo.ref() : 0;
+}
+
+nsresult InternalResponse::GeneratePaddingInfo() {
+ MOZ_DIAGNOSTIC_ASSERT(mType == ResponseType::Opaque);
+ MOZ_DIAGNOSTIC_ASSERT(mPaddingSize == UNKNOWN_PADDING_SIZE);
+
+ // Utilize random generator to generator a random number
+ nsresult rv;
+ uint32_t randomNumber = 0;
+ nsCOMPtr<nsIRandomGenerator> randomGenerator =
+ do_GetService("@mozilla.org/security/random-generator;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Maybe<uint64_t> maybeRandomNum = RandomUint64();
+ if (maybeRandomNum.isSome()) {
+ mPaddingInfo.emplace(uint32_t(maybeRandomNum.value() % kMaxRandomNumber));
+ return NS_OK;
+ }
+ return rv;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(randomGenerator);
+
+ uint8_t* buffer;
+ rv = randomGenerator->GenerateRandomBytes(sizeof(randomNumber), &buffer);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Maybe<uint64_t> maybeRandomNum = RandomUint64();
+ if (maybeRandomNum.isSome()) {
+ mPaddingInfo.emplace(uint32_t(maybeRandomNum.value() % kMaxRandomNumber));
+ return NS_OK;
+ }
+ return rv;
+ }
+
+ memcpy(&randomNumber, buffer, sizeof(randomNumber));
+ free(buffer);
+
+ mPaddingInfo.emplace(randomNumber % kMaxRandomNumber);
+
+ return rv;
+}
+
+int64_t InternalResponse::GetPaddingSize() {
+ // We initialize padding size to an unknown size (-1). After cached, we only
+ // pad opaque response. Opaque response's padding size might be unknown before
+ // cached.
+ MOZ_DIAGNOSTIC_ASSERT(mType == ResponseType::Opaque ||
+ mPaddingSize == UNKNOWN_PADDING_SIZE);
+ MOZ_DIAGNOSTIC_ASSERT(mPaddingSize == UNKNOWN_PADDING_SIZE ||
+ mPaddingSize >= 0);
+
+ return mPaddingSize;
+}
+
+void InternalResponse::SetPaddingSize(int64_t aPaddingSize) {
+ // We should only pad the opaque response.
+ MOZ_DIAGNOSTIC_ASSERT(
+ (mType == ResponseType::Opaque) !=
+ (aPaddingSize == InternalResponse::UNKNOWN_PADDING_SIZE));
+ MOZ_DIAGNOSTIC_ASSERT(aPaddingSize == UNKNOWN_PADDING_SIZE ||
+ aPaddingSize >= 0);
+
+ mPaddingSize = aPaddingSize;
+}
+
+void InternalResponse::SetPrincipalInfo(
+ UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo) {
+ mPrincipalInfo = std::move(aPrincipalInfo);
+}
+
+LoadTainting InternalResponse::GetTainting() const {
+ switch (mType) {
+ case ResponseType::Cors:
+ return LoadTainting::CORS;
+ case ResponseType::Opaque:
+ return LoadTainting::Opaque;
+ default:
+ return LoadTainting::Basic;
+ }
+}
+
+already_AddRefed<InternalResponse> InternalResponse::Unfiltered() {
+ RefPtr<InternalResponse> ref = mWrappedResponse;
+ if (!ref) {
+ ref = this;
+ }
+ return ref.forget();
+}
+
+already_AddRefed<InternalResponse> InternalResponse::OpaqueResponse() {
+ MOZ_ASSERT(!mWrappedResponse,
+ "Can't OpaqueResponse a already wrapped response");
+ RefPtr<InternalResponse> response = new InternalResponse(0, ""_ns);
+ response->mType = ResponseType::Opaque;
+ response->mChannelInfo = mChannelInfo;
+ if (mPrincipalInfo) {
+ response->mPrincipalInfo =
+ MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);
+ }
+ response->mWrappedResponse = this;
+ return response.forget();
+}
+
+already_AddRefed<InternalResponse> InternalResponse::OpaqueRedirectResponse() {
+ MOZ_ASSERT(!mWrappedResponse,
+ "Can't OpaqueRedirectResponse a already wrapped response");
+ MOZ_ASSERT(!mURLList.IsEmpty(),
+ "URLList should not be emtpy for internalResponse");
+ RefPtr<InternalResponse> response = OpaqueResponse();
+ response->mType = ResponseType::Opaqueredirect;
+ response->mURLList = mURLList.Clone();
+ return response.forget();
+}
+
+already_AddRefed<InternalResponse> InternalResponse::CreateIncompleteCopy() {
+ RefPtr<InternalResponse> copy = new InternalResponse(mStatus, mStatusText);
+ copy->mType = mType;
+ copy->mURLList = mURLList.Clone();
+ copy->mChannelInfo = mChannelInfo;
+ if (mPrincipalInfo) {
+ copy->mPrincipalInfo =
+ MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);
+ }
+ return copy.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/InternalResponse.h b/dom/fetch/InternalResponse.h
new file mode 100644
index 0000000000..f256d3ec5b
--- /dev/null
+++ b/dom/fetch/InternalResponse.h
@@ -0,0 +1,383 @@
+/* -*- 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_InternalResponse_h
+#define mozilla_dom_InternalResponse_h
+
+#include "nsIInputStream.h"
+#include "nsICacheInfoChannel.h"
+#include "nsISupportsImpl.h"
+#include "nsProxyRelease.h"
+
+#include "mozilla/dom/InternalHeaders.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/ResponseBinding.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace ipc {
+class AutoIPCStream;
+class PBackgroundChild;
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class IPCInternalResponse;
+class InternalHeaders;
+
+class InternalResponse final {
+ friend class FetchDriver;
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalResponse)
+
+ InternalResponse(
+ uint16_t aStatus, const nsACString& aStatusText,
+ RequestCredentials aCredentialsMode = RequestCredentials::Omit);
+
+ static RefPtr<InternalResponse> FromIPC(
+ const IPCInternalResponse& aIPCResponse);
+
+ // Note: the AutoIPCStreams must outlive the IPCInternalResponse.
+ void ToIPC(
+ IPCInternalResponse* aIPCResponse,
+ mozilla::ipc::PBackgroundChild* aManager,
+ UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoBodyStream,
+ UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoAlternativeBodyStream);
+
+ enum CloneType {
+ eCloneInputStream,
+ eDontCloneInputStream,
+ };
+
+ already_AddRefed<InternalResponse> Clone(CloneType eCloneType);
+
+ static already_AddRefed<InternalResponse> NetworkError(nsresult aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aRv));
+ RefPtr<InternalResponse> response = new InternalResponse(0, ""_ns);
+ ErrorResult result;
+ response->Headers()->SetGuard(HeadersGuardEnum::Immutable, result);
+ MOZ_ASSERT(!result.Failed());
+ response->mType = ResponseType::Error;
+ response->mErrorCode = aRv;
+ return response.forget();
+ }
+
+ already_AddRefed<InternalResponse> OpaqueResponse();
+
+ already_AddRefed<InternalResponse> OpaqueRedirectResponse();
+
+ already_AddRefed<InternalResponse> BasicResponse();
+
+ already_AddRefed<InternalResponse> CORSResponse();
+
+ ResponseType Type() const {
+ MOZ_ASSERT_IF(mType == ResponseType::Error, !mWrappedResponse);
+ MOZ_ASSERT_IF(mType == ResponseType::Default, !mWrappedResponse);
+ MOZ_ASSERT_IF(mType == ResponseType::Basic, mWrappedResponse);
+ MOZ_ASSERT_IF(mType == ResponseType::Cors, mWrappedResponse);
+ MOZ_ASSERT_IF(mType == ResponseType::Opaque, mWrappedResponse);
+ MOZ_ASSERT_IF(mType == ResponseType::Opaqueredirect, mWrappedResponse);
+ return mType;
+ }
+
+ bool IsError() const { return Type() == ResponseType::Error; }
+ // GetUrl should return last fetch URL in response's url list and null if
+ // response's url list is the empty list.
+ const nsCString& GetURL() const {
+ // Empty urlList when response is a synthetic response.
+ if (mURLList.IsEmpty()) {
+ return EmptyCString();
+ }
+ return mURLList.LastElement();
+ }
+ void GetURLList(nsTArray<nsCString>& aURLList) const {
+ aURLList.Assign(mURLList);
+ }
+ const nsCString& GetUnfilteredURL() const {
+ if (mWrappedResponse) {
+ return mWrappedResponse->GetURL();
+ }
+ return GetURL();
+ }
+ void GetUnfilteredURLList(nsTArray<nsCString>& aURLList) const {
+ if (mWrappedResponse) {
+ return mWrappedResponse->GetURLList(aURLList);
+ }
+
+ return GetURLList(aURLList);
+ }
+
+ nsTArray<nsCString> GetUnfilteredURLList() const {
+ nsTArray<nsCString> list;
+ GetUnfilteredURLList(list);
+ return list;
+ }
+
+ void SetURLList(const nsTArray<nsCString>& aURLList) {
+ mURLList.Assign(aURLList);
+
+#ifdef DEBUG
+ for (uint32_t i = 0; i < mURLList.Length(); ++i) {
+ MOZ_ASSERT(mURLList[i].Find("#"_ns) == kNotFound);
+ }
+#endif
+ }
+
+ uint16_t GetStatus() const { return mStatus; }
+
+ uint16_t GetUnfilteredStatus() const {
+ if (mWrappedResponse) {
+ return mWrappedResponse->GetStatus();
+ }
+
+ return GetStatus();
+ }
+
+ const nsCString& GetStatusText() const { return mStatusText; }
+
+ const nsCString& GetUnfilteredStatusText() const {
+ if (mWrappedResponse) {
+ return mWrappedResponse->GetStatusText();
+ }
+
+ return GetStatusText();
+ }
+
+ InternalHeaders* Headers() { return mHeaders; }
+
+ InternalHeaders* UnfilteredHeaders() {
+ if (mWrappedResponse) {
+ return mWrappedResponse->Headers();
+ };
+
+ return Headers();
+ }
+
+ void GetUnfilteredBody(nsIInputStream** aStream,
+ int64_t* aBodySize = nullptr) {
+ if (mWrappedResponse) {
+ MOZ_ASSERT(!mBody);
+ return mWrappedResponse->GetBody(aStream, aBodySize);
+ }
+ nsCOMPtr<nsIInputStream> stream = mBody;
+ stream.forget(aStream);
+ if (aBodySize) {
+ *aBodySize = mBodySize;
+ }
+ }
+
+ void GetBody(nsIInputStream** aStream, int64_t* aBodySize = nullptr) {
+ if (Type() == ResponseType::Opaque ||
+ Type() == ResponseType::Opaqueredirect) {
+ *aStream = nullptr;
+ if (aBodySize) {
+ *aBodySize = UNKNOWN_BODY_SIZE;
+ }
+ return;
+ }
+
+ GetUnfilteredBody(aStream, aBodySize);
+ }
+
+ void SetBodyBlobURISpec(nsACString& aBlobURISpec) {
+ mBodyBlobURISpec = aBlobURISpec;
+ }
+
+ const nsACString& BodyBlobURISpec() const {
+ if (mWrappedResponse) {
+ return mWrappedResponse->BodyBlobURISpec();
+ }
+ return mBodyBlobURISpec;
+ }
+
+ void SetBodyLocalPath(nsAString& aLocalPath) { mBodyLocalPath = aLocalPath; }
+
+ const nsAString& BodyLocalPath() const {
+ if (mWrappedResponse) {
+ return mWrappedResponse->BodyLocalPath();
+ }
+ return mBodyLocalPath;
+ }
+
+ void SetBody(nsIInputStream* aBody, int64_t aBodySize) {
+ if (mWrappedResponse) {
+ return mWrappedResponse->SetBody(aBody, aBodySize);
+ }
+ // A request's body may not be reset once set.
+ MOZ_ASSERT(!mBody);
+ MOZ_ASSERT(mBodySize == UNKNOWN_BODY_SIZE);
+ // Check arguments.
+ MOZ_ASSERT(aBodySize == UNKNOWN_BODY_SIZE || aBodySize >= 0);
+ // If body is not given, then size must be unknown.
+ MOZ_ASSERT_IF(!aBody, aBodySize == UNKNOWN_BODY_SIZE);
+
+ mBody = aBody;
+ mBodySize = aBodySize;
+ }
+
+ uint32_t GetPaddingInfo();
+
+ nsresult GeneratePaddingInfo();
+
+ int64_t GetPaddingSize();
+
+ void SetPaddingSize(int64_t aPaddingSize);
+
+ void SetAlternativeDataType(const nsACString& aAltDataType) {
+ if (mWrappedResponse) {
+ return mWrappedResponse->SetAlternativeDataType(aAltDataType);
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mAlternativeDataType.IsEmpty());
+
+ mAlternativeDataType.Assign(aAltDataType);
+ }
+
+ const nsCString& GetAlternativeDataType() {
+ if (mWrappedResponse) {
+ return mWrappedResponse->GetAlternativeDataType();
+ }
+
+ return mAlternativeDataType;
+ }
+
+ void SetAlternativeBody(nsIInputStream* aAlternativeBody) {
+ if (mWrappedResponse) {
+ return mWrappedResponse->SetAlternativeBody(aAlternativeBody);
+ }
+ // A request's body may not be reset once set.
+ MOZ_DIAGNOSTIC_ASSERT(!mAlternativeBody);
+
+ mAlternativeBody = aAlternativeBody;
+ }
+
+ already_AddRefed<nsIInputStream> TakeAlternativeBody() {
+ if (mWrappedResponse) {
+ return mWrappedResponse->TakeAlternativeBody();
+ }
+
+ if (!mAlternativeBody) {
+ return nullptr;
+ }
+
+ // cleanup the non-alternative body here.
+ // Once alternative data is used, the real body is no need anymore.
+ mBody = nullptr;
+ mBodySize = UNKNOWN_BODY_SIZE;
+ return mAlternativeBody.forget();
+ }
+
+ void SetCacheInfoChannel(
+ const nsMainThreadPtrHandle<nsICacheInfoChannel>& aCacheInfoChannel) {
+ if (mWrappedResponse) {
+ return mWrappedResponse->SetCacheInfoChannel(aCacheInfoChannel);
+ }
+ MOZ_ASSERT(!mCacheInfoChannel);
+ mCacheInfoChannel = aCacheInfoChannel;
+ }
+
+ nsMainThreadPtrHandle<nsICacheInfoChannel> TakeCacheInfoChannel() {
+ if (mWrappedResponse) {
+ return mWrappedResponse->TakeCacheInfoChannel();
+ }
+ nsMainThreadPtrHandle<nsICacheInfoChannel> rtn = mCacheInfoChannel;
+ mCacheInfoChannel = nullptr;
+ return rtn;
+ }
+
+ bool HasCacheInfoChannel() const {
+ if (mWrappedResponse) {
+ return !!mWrappedResponse->HasCacheInfoChannel();
+ }
+ return !!mCacheInfoChannel;
+ }
+
+ void InitChannelInfo(nsIChannel* aChannel) {
+ mChannelInfo.InitFromChannel(aChannel);
+ }
+
+ void InitChannelInfo(const mozilla::ipc::IPCChannelInfo& aChannelInfo) {
+ mChannelInfo.InitFromIPCChannelInfo(aChannelInfo);
+ }
+
+ void InitChannelInfo(const ChannelInfo& aChannelInfo) {
+ mChannelInfo = aChannelInfo;
+ }
+
+ const ChannelInfo& GetChannelInfo() const { return mChannelInfo; }
+
+ const UniquePtr<mozilla::ipc::PrincipalInfo>& GetPrincipalInfo() const {
+ return mPrincipalInfo;
+ }
+
+ bool IsRedirected() const { return mURLList.Length() > 1; }
+
+ nsresult GetErrorCode() const { return mErrorCode; }
+
+ // Takes ownership of the principal info.
+ void SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo);
+
+ LoadTainting GetTainting() const;
+
+ already_AddRefed<InternalResponse> Unfiltered();
+
+ private:
+ ~InternalResponse();
+
+ explicit InternalResponse(const InternalResponse& aOther) = delete;
+ InternalResponse& operator=(const InternalResponse&) = delete;
+
+ // Returns an instance of InternalResponse which is a copy of this
+ // InternalResponse, except headers, body and wrapped response (if any) which
+ // are left uninitialized. Used for cloning and filtering.
+ already_AddRefed<InternalResponse> CreateIncompleteCopy();
+
+ ResponseType mType;
+ // A response has an associated url list (a list of zero or more fetch URLs).
+ // Unless stated otherwise, it is the empty list. The current url is the last
+ // element in mURLlist
+ nsTArray<nsCString> mURLList;
+ const uint16_t mStatus;
+ const nsCString mStatusText;
+ RefPtr<InternalHeaders> mHeaders;
+ nsCOMPtr<nsIInputStream> mBody;
+ nsCString mBodyBlobURISpec;
+ nsString mBodyLocalPath;
+ int64_t mBodySize;
+ // It's used to passed to the CacheResponse to generate padding size. Once, we
+ // generate the padding size for resposne, we don't need it anymore.
+ Maybe<uint32_t> mPaddingInfo;
+ int64_t mPaddingSize;
+ nsresult mErrorCode;
+ RequestCredentials mCredentialsMode;
+
+ // For alternative data such as JS Bytecode cached in the HTTP cache.
+ nsCString mAlternativeDataType;
+ nsCOMPtr<nsIInputStream> mAlternativeBody;
+ nsMainThreadPtrHandle<nsICacheInfoChannel> mCacheInfoChannel;
+
+ public:
+ static const int64_t UNKNOWN_BODY_SIZE = -1;
+ static const int64_t UNKNOWN_PADDING_SIZE = -1;
+
+ private:
+ ChannelInfo mChannelInfo;
+ UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
+
+ // For filtered responses.
+ // Cache, and SW interception should always serialize/access the underlying
+ // unfiltered headers and when deserializing, create an InternalResponse
+ // with the unfiltered headers followed by wrapping it.
+ RefPtr<InternalResponse> mWrappedResponse;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_InternalResponse_h
diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp
new file mode 100644
index 0000000000..62041cdc10
--- /dev/null
+++ b/dom/fetch/Request.cpp
@@ -0,0 +1,662 @@
+/* -*- 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 "Request.h"
+
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/Fetch.h"
+#include "mozilla/dom/FetchUtil.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/URL.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ADDREF_INHERITED(Request, FetchBody<Request>)
+NS_IMPL_RELEASE_INHERITED(Request, FetchBody<Request>)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Request)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Request, FetchBody<Request>)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignal)
+ AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Request, FetchBody<Request>)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignal)
+ AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Request, FetchBody<Request>)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamBody)
+ MOZ_DIAGNOSTIC_ASSERT(!tmp->mReadableStreamReader);
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(FetchBody<Request>)
+
+Request::Request(nsIGlobalObject* aOwner, SafeRefPtr<InternalRequest> aRequest,
+ AbortSignal* aSignal)
+ : FetchBody<Request>(aOwner), mRequest(std::move(aRequest)) {
+ MOZ_ASSERT(mRequest->Headers()->Guard() == HeadersGuardEnum::Immutable ||
+ mRequest->Headers()->Guard() == HeadersGuardEnum::Request ||
+ mRequest->Headers()->Guard() == HeadersGuardEnum::Request_no_cors);
+ if (aSignal) {
+ // If we don't have a signal as argument, we will create it when required by
+ // content, otherwise the Request's signal must follow what has been passed.
+ mSignal = new AbortSignal(aOwner, aSignal->Aborted());
+ if (!mSignal->Aborted()) {
+ mSignal->Follow(aSignal);
+ }
+ }
+}
+
+Request::~Request() = default;
+
+SafeRefPtr<InternalRequest> Request::GetInternalRequest() {
+ return mRequest.clonePtr();
+}
+
+namespace {
+already_AddRefed<nsIURI> ParseURLFromDocument(Document* aDocument,
+ const nsAString& aInput,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aDocument);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
+ nsAutoCString input;
+ if (!AppendUTF16toUTF8(aInput, input, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(resolvedURI), input, nullptr,
+ aDocument->GetBaseURI());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(input);
+ }
+ return resolvedURI.forget();
+}
+void GetRequestURLFromDocument(Document* aDocument, const nsAString& aInput,
+ nsAString& aRequestURL, nsACString& aURLfragment,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIURI> resolvedURI = ParseURLFromDocument(aDocument, aInput, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ // This fails with URIs with weird protocols, even when they are valid,
+ // so we ignore the failure
+ nsAutoCString credentials;
+ Unused << resolvedURI->GetUserPass(credentials);
+ if (!credentials.IsEmpty()) {
+ aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(NS_ConvertUTF16toUTF8(aInput));
+ return;
+ }
+
+ nsCOMPtr<nsIURI> resolvedURIClone;
+ aRv = NS_GetURIWithoutRef(resolvedURI, getter_AddRefs(resolvedURIClone));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ nsAutoCString spec;
+ aRv = resolvedURIClone->GetSpec(spec);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ CopyUTF8toUTF16(spec, aRequestURL);
+
+ // Get the fragment from nsIURI.
+ aRv = resolvedURI->GetRef(aURLfragment);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+}
+already_AddRefed<nsIURI> ParseURLFromChrome(const nsAString& aInput,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
+ nsAutoCString input;
+ if (!AppendUTF16toUTF8(aInput, input, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), input);
+ if (NS_FAILED(rv)) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(input);
+ }
+ return uri.forget();
+}
+void GetRequestURLFromChrome(const nsAString& aInput, nsAString& aRequestURL,
+ nsACString& aURLfragment, ErrorResult& aRv) {
+ nsCOMPtr<nsIURI> uri = ParseURLFromChrome(aInput, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ // This fails with URIs with weird protocols, even when they are valid,
+ // so we ignore the failure
+ nsAutoCString credentials;
+ Unused << uri->GetUserPass(credentials);
+ if (!credentials.IsEmpty()) {
+ aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(NS_ConvertUTF16toUTF8(aInput));
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uriClone;
+ aRv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriClone));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ nsAutoCString spec;
+ aRv = uriClone->GetSpec(spec);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ CopyUTF8toUTF16(spec, aRequestURL);
+
+ // Get the fragment from nsIURI.
+ aRv = uri->GetRef(aURLfragment);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+}
+already_AddRefed<URL> ParseURLFromWorker(nsIGlobalObject* aGlobal,
+ const nsAString& aInput,
+ ErrorResult& aRv) {
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+
+ NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
+ RefPtr<URL> url = URL::Constructor(aGlobal, aInput, baseURL, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(NS_ConvertUTF16toUTF8(aInput));
+ }
+ return url.forget();
+}
+void GetRequestURLFromWorker(nsIGlobalObject* aGlobal, const nsAString& aInput,
+ nsAString& aRequestURL, nsACString& aURLfragment,
+ ErrorResult& aRv) {
+ RefPtr<URL> url = ParseURLFromWorker(aGlobal, aInput, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ nsString username;
+ url->GetUsername(username);
+
+ nsString password;
+ url->GetPassword(password);
+
+ if (!username.IsEmpty() || !password.IsEmpty()) {
+ aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(NS_ConvertUTF16toUTF8(aInput));
+ return;
+ }
+
+ // Get the fragment from URL.
+ nsAutoString fragment;
+ url->GetHash(fragment);
+
+ // Note: URL::GetHash() includes the "#" and we want the fragment with out
+ // the hash symbol.
+ if (!fragment.IsEmpty()) {
+ CopyUTF16toUTF8(Substring(fragment, 1), aURLfragment);
+ }
+
+ url->SetHash(u""_ns);
+ url->GetHref(aRequestURL);
+}
+
+class ReferrerSameOriginChecker final : public WorkerMainThreadRunnable {
+ public:
+ ReferrerSameOriginChecker(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aReferrerURL, nsresult& aResult)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ "Fetch :: Referrer same origin check"_ns),
+ mReferrerURL(aReferrerURL),
+ mResult(aResult) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool MainThreadRun() override {
+ nsCOMPtr<nsIURI> uri;
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), mReferrerURL))) {
+ nsCOMPtr<nsIPrincipal> principal = mWorkerPrivate->GetPrincipal();
+ if (principal) {
+ mResult = principal->CheckMayLoad(uri,
+ /* allowIfInheritsPrincipal */ false);
+ }
+ }
+ return true;
+ }
+
+ private:
+ const nsString mReferrerURL;
+ nsresult& mResult;
+};
+
+} // namespace
+
+/*static*/
+SafeRefPtr<Request> Request::Constructor(const GlobalObject& aGlobal,
+ const RequestOrUSVString& aInput,
+ const RequestInit& aInit,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ return Constructor(global, aGlobal.Context(), aInput, aInit, aRv);
+}
+
+/*static*/
+SafeRefPtr<Request> Request::Constructor(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const RequestOrUSVString& aInput,
+ const RequestInit& aInit,
+ ErrorResult& aRv) {
+ bool hasCopiedBody = false;
+ SafeRefPtr<InternalRequest> request;
+
+ RefPtr<AbortSignal> signal;
+
+ if (aInput.IsRequest()) {
+ RefPtr<Request> inputReq = &aInput.GetAsRequest();
+ nsCOMPtr<nsIInputStream> body;
+ inputReq->GetBody(getter_AddRefs(body));
+ bool used = inputReq->GetBodyUsed(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ if (used) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ // The body will be copied when GetRequestConstructorCopy() is executed.
+ if (body) {
+ hasCopiedBody = true;
+ }
+
+ request = inputReq->GetInternalRequest();
+ signal = inputReq->GetOrCreateSignal();
+ } else {
+ // aInput is USVString.
+ // We need to get url before we create a InternalRequest.
+ nsAutoString input;
+ input.Assign(aInput.GetAsUSVString());
+ nsAutoString requestURL;
+ nsCString fragment;
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> inner(do_QueryInterface(aGlobal));
+ Document* doc = inner ? inner->GetExtantDoc() : nullptr;
+ if (doc) {
+ GetRequestURLFromDocument(doc, input, requestURL, fragment, aRv);
+ } else {
+ // If we don't have a document, we must assume that this is a full URL.
+ GetRequestURLFromChrome(input, requestURL, fragment, aRv);
+ }
+ } else {
+ GetRequestURLFromWorker(aGlobal, input, requestURL, fragment, aRv);
+ }
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ request = MakeSafeRefPtr<InternalRequest>(NS_ConvertUTF16toUTF8(requestURL),
+ fragment);
+ }
+ request = request->GetRequestConstructorCopy(aGlobal, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ RequestMode fallbackMode = RequestMode::EndGuard_;
+ RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_;
+ RequestCache fallbackCache = RequestCache::EndGuard_;
+ if (aInput.IsUSVString()) {
+ fallbackMode = RequestMode::Cors;
+ fallbackCredentials = RequestCredentials::Same_origin;
+ fallbackCache = RequestCache::Default;
+ }
+
+ RequestMode mode =
+ aInit.mMode.WasPassed() ? aInit.mMode.Value() : fallbackMode;
+ RequestCredentials credentials = aInit.mCredentials.WasPassed()
+ ? aInit.mCredentials.Value()
+ : fallbackCredentials;
+
+ if (mode == RequestMode::Navigate) {
+ aRv.ThrowTypeError<MSG_INVALID_REQUEST_MODE>("navigate");
+ return nullptr;
+ }
+ if (aInit.IsAnyMemberPresent() && request->Mode() == RequestMode::Navigate) {
+ mode = RequestMode::Same_origin;
+ }
+
+ if (aInit.IsAnyMemberPresent()) {
+ request->SetReferrer(
+ NS_LITERAL_STRING_FROM_CSTRING(kFETCH_CLIENT_REFERRER_STR));
+ request->SetReferrerPolicy(ReferrerPolicy::_empty);
+ }
+ if (aInit.mReferrer.WasPassed()) {
+ const nsString& referrer = aInit.mReferrer.Value();
+ if (referrer.IsEmpty()) {
+ request->SetReferrer(u""_ns);
+ } else {
+ nsAutoString referrerURL;
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> inner(do_QueryInterface(aGlobal));
+ Document* doc = inner ? inner->GetExtantDoc() : nullptr;
+ nsCOMPtr<nsIURI> uri;
+ if (doc) {
+ uri = ParseURLFromDocument(doc, referrer, aRv);
+ } else {
+ // If we don't have a document, we must assume that this is a full
+ // URL.
+ uri = ParseURLFromChrome(referrer, aRv);
+ }
+ if (NS_WARN_IF(aRv.Failed())) {
+ aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(
+ NS_ConvertUTF16toUTF8(referrer));
+ return nullptr;
+ }
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ CopyUTF8toUTF16(spec, referrerURL);
+ if (!referrerURL.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
+ nsCOMPtr<nsIPrincipal> principal = aGlobal->PrincipalOrNull();
+ if (principal) {
+ nsresult rv =
+ principal->CheckMayLoad(uri,
+ /* allowIfInheritsPrincipal */ false);
+ if (NS_FAILED(rv)) {
+ referrerURL.AssignLiteral(kFETCH_CLIENT_REFERRER_STR);
+ }
+ }
+ }
+ } else {
+ RefPtr<URL> url = ParseURLFromWorker(aGlobal, referrer, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(
+ NS_ConvertUTF16toUTF8(referrer));
+ return nullptr;
+ }
+ url->GetHref(referrerURL);
+ if (!referrerURL.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ nsresult rv = NS_OK;
+ // ReferrerSameOriginChecker uses a sync loop to get the main thread
+ // to perform the same-origin check. Overall, on Workers this method
+ // can create 3 sync loops (two for constructing URLs and one here) so
+ // in the future we may want to optimize it all by off-loading all of
+ // this work in a single sync loop.
+ RefPtr<ReferrerSameOriginChecker> checker =
+ new ReferrerSameOriginChecker(worker, referrerURL, rv);
+ IgnoredErrorResult error;
+ checker->Dispatch(Canceling, error);
+ if (error.Failed() || NS_FAILED(rv)) {
+ referrerURL.AssignLiteral(kFETCH_CLIENT_REFERRER_STR);
+ }
+ }
+ }
+ request->SetReferrer(referrerURL);
+ }
+ }
+
+ if (aInit.mReferrerPolicy.WasPassed()) {
+ request->SetReferrerPolicy(aInit.mReferrerPolicy.Value());
+ }
+
+ if (aInit.mSignal.WasPassed()) {
+ signal = aInit.mSignal.Value();
+ }
+
+ UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo;
+ nsILoadInfo::CrossOriginEmbedderPolicy coep =
+ nsILoadInfo::EMBEDDER_POLICY_NULL;
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
+ if (window) {
+ nsCOMPtr<Document> doc;
+ doc = window->GetExtantDoc();
+ if (doc) {
+ request->SetEnvironmentReferrerPolicy(doc->GetReferrerPolicy());
+
+ principalInfo.reset(new mozilla::ipc::PrincipalInfo());
+ nsresult rv =
+ PrincipalToPrincipalInfo(doc->NodePrincipal(), principalInfo.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+ }
+ if (window->GetWindowContext()) {
+ coep = window->GetWindowContext()->GetEmbedderPolicy();
+ }
+ }
+ } else {
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ if (worker) {
+ worker->AssertIsOnWorkerThread();
+ request->SetEnvironmentReferrerPolicy(worker->GetReferrerPolicy());
+ principalInfo =
+ MakeUnique<mozilla::ipc::PrincipalInfo>(worker->GetPrincipalInfo());
+ coep = worker->GetEmbedderPolicy();
+ // For dedicated worker, the response must respect the owner's COEP.
+ if (coep == nsILoadInfo::EMBEDDER_POLICY_NULL &&
+ worker->IsDedicatedWorker()) {
+ coep = worker->GetOwnerEmbedderPolicy();
+ }
+ }
+ }
+
+ request->SetPrincipalInfo(std::move(principalInfo));
+ request->SetEmbedderPolicy(coep);
+
+ if (mode != RequestMode::EndGuard_) {
+ request->SetMode(mode);
+ }
+
+ if (credentials != RequestCredentials::EndGuard_) {
+ request->SetCredentialsMode(credentials);
+ }
+
+ RequestCache cache =
+ aInit.mCache.WasPassed() ? aInit.mCache.Value() : fallbackCache;
+ if (cache != RequestCache::EndGuard_) {
+ if (cache == RequestCache::Only_if_cached &&
+ request->Mode() != RequestMode::Same_origin) {
+ nsCString modeString(RequestModeValues::GetString(request->Mode()));
+ aRv.ThrowTypeError<MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN>(modeString);
+ return nullptr;
+ }
+ request->SetCacheMode(cache);
+ }
+
+ if (aInit.mRedirect.WasPassed()) {
+ request->SetRedirectMode(aInit.mRedirect.Value());
+ }
+
+ if (aInit.mIntegrity.WasPassed()) {
+ request->SetIntegrity(aInit.mIntegrity.Value());
+ }
+
+ if (aInit.mMozErrors.WasPassed() && aInit.mMozErrors.Value()) {
+ request->SetMozErrors();
+ }
+
+ // Request constructor step 14.
+ if (aInit.mMethod.WasPassed()) {
+ nsAutoCString method(aInit.mMethod.Value());
+
+ // Step 14.1. Disallow forbidden methods, and anything that is not a HTTP
+ // token, since HTTP states that Method may be any of the defined values or
+ // a token (extension method).
+ nsAutoCString outMethod;
+ nsresult rv = FetchUtil::GetValidRequestMethod(method, outMethod);
+ if (NS_FAILED(rv)) {
+ aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(method);
+ return nullptr;
+ }
+
+ // Step 14.2
+ request->SetMethod(outMethod);
+ }
+
+ RefPtr<InternalHeaders> requestHeaders = request->Headers();
+
+ RefPtr<InternalHeaders> headers;
+ if (aInit.mHeaders.WasPassed()) {
+ RefPtr<Headers> h = Headers::Create(aGlobal, aInit.mHeaders.Value(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ headers = h->GetInternalHeaders();
+ } else {
+ headers = new InternalHeaders(*requestHeaders);
+ }
+
+ requestHeaders->Clear();
+ // From "Let r be a new Request object associated with request and a new
+ // Headers object whose guard is "request"."
+ requestHeaders->SetGuard(HeadersGuardEnum::Request, aRv);
+ MOZ_ASSERT(!aRv.Failed());
+
+ if (request->Mode() == RequestMode::No_cors) {
+ if (!request->HasSimpleMethod()) {
+ nsAutoCString method;
+ request->GetMethod(method);
+ aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(method);
+ return nullptr;
+ }
+
+ requestHeaders->SetGuard(HeadersGuardEnum::Request_no_cors, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ requestHeaders->Fill(*headers, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if ((aInit.mBody.WasPassed() && !aInit.mBody.Value().IsNull()) ||
+ hasCopiedBody) {
+ // HEAD and GET are not allowed to have a body.
+ nsAutoCString method;
+ request->GetMethod(method);
+ // method is guaranteed to be uppercase due to step 14.2 above.
+ if (method.EqualsLiteral("HEAD") || method.EqualsLiteral("GET")) {
+ aRv.ThrowTypeError("HEAD or GET Request cannot have a body.");
+ return nullptr;
+ }
+ }
+
+ if (aInit.mBody.WasPassed()) {
+ const Nullable<fetch::OwningBodyInit>& bodyInitNullable =
+ aInit.mBody.Value();
+ if (!bodyInitNullable.IsNull()) {
+ const fetch::OwningBodyInit& bodyInit = bodyInitNullable.Value();
+ nsCOMPtr<nsIInputStream> stream;
+ nsAutoCString contentTypeWithCharset;
+ uint64_t contentLength = 0;
+ aRv = ExtractByteStreamFromBody(bodyInit, getter_AddRefs(stream),
+ contentTypeWithCharset, contentLength);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIInputStream> temporaryBody = stream;
+
+ if (!contentTypeWithCharset.IsVoid() &&
+ !requestHeaders->Has("Content-Type"_ns, aRv)) {
+ requestHeaders->Append("Content-Type"_ns, contentTypeWithCharset, aRv);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (hasCopiedBody) {
+ request->SetBody(nullptr, 0);
+ }
+
+ request->SetBody(temporaryBody, contentLength);
+ }
+ }
+
+ auto domRequest =
+ MakeSafeRefPtr<Request>(aGlobal, std::move(request), signal);
+
+ if (aInput.IsRequest()) {
+ RefPtr<Request> inputReq = &aInput.GetAsRequest();
+ nsCOMPtr<nsIInputStream> body;
+ inputReq->GetBody(getter_AddRefs(body));
+ if (body) {
+ inputReq->SetBody(nullptr, 0);
+ inputReq->SetBodyUsed(aCx, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+ }
+ return domRequest;
+}
+
+SafeRefPtr<Request> Request::Clone(ErrorResult& aRv) {
+ bool used = GetBodyUsed(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ if (used) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ SafeRefPtr<InternalRequest> ir = mRequest->Clone();
+ if (!ir) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return MakeSafeRefPtr<Request>(mOwner, std::move(ir), GetOrCreateSignal());
+}
+
+Headers* Request::Headers_() {
+ if (!mHeaders) {
+ mHeaders = new Headers(mOwner, mRequest->Headers());
+ }
+
+ return mHeaders;
+}
+
+AbortSignal* Request::GetOrCreateSignal() {
+ if (!mSignal) {
+ mSignal = new AbortSignal(mOwner, false);
+ }
+
+ return mSignal;
+}
+
+AbortSignalImpl* Request::GetSignalImpl() const { return mSignal; }
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/Request.h b/dom/fetch/Request.h
new file mode 100644
index 0000000000..4b5ce08cc6
--- /dev/null
+++ b/dom/fetch/Request.h
@@ -0,0 +1,143 @@
+/* -*- 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_Request_h
+#define mozilla_dom_Request_h
+
+#include "nsISupportsImpl.h"
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/Fetch.h"
+#include "mozilla/dom/InternalRequest.h"
+// Required here due to certain WebIDL enums/classes being declared in both
+// files.
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/SafeRefPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class Headers;
+class InternalHeaders;
+class RequestOrUSVString;
+
+class Request final : public FetchBody<Request>, public nsWrapperCache {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(Request,
+ FetchBody<Request>)
+
+ public:
+ Request(nsIGlobalObject* aOwner, SafeRefPtr<InternalRequest> aRequest,
+ AbortSignal* aSignal);
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return Request_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ void GetUrl(nsAString& aUrl) const {
+ nsAutoCString url;
+ mRequest->GetURL(url);
+ CopyUTF8toUTF16(url, aUrl);
+ }
+
+ void GetMethod(nsCString& aMethod) const { aMethod = mRequest->mMethod; }
+
+ RequestMode Mode() const { return mRequest->mMode; }
+
+ RequestCredentials Credentials() const { return mRequest->mCredentialsMode; }
+
+ RequestCache Cache() const { return mRequest->GetCacheMode(); }
+
+ RequestRedirect Redirect() const { return mRequest->GetRedirectMode(); }
+
+ void GetIntegrity(nsAString& aIntegrity) const {
+ aIntegrity = mRequest->GetIntegrity();
+ }
+
+ bool MozErrors() const { return mRequest->MozErrors(); }
+
+ RequestDestination Destination() const { return mRequest->Destination(); }
+
+ void OverrideContentPolicyType(uint32_t aContentPolicyType) {
+ mRequest->OverrideContentPolicyType(
+ static_cast<nsContentPolicyType>(aContentPolicyType));
+ }
+
+ bool IsContentPolicyTypeOverridden() const {
+ return mRequest->IsContentPolicyTypeOverridden();
+ }
+
+ void GetReferrer(nsAString& aReferrer) const {
+ mRequest->GetReferrer(aReferrer);
+ }
+
+ ReferrerPolicy ReferrerPolicy_() const { return mRequest->ReferrerPolicy_(); }
+
+ InternalHeaders* GetInternalHeaders() const { return mRequest->Headers(); }
+
+ Headers* Headers_();
+
+ using FetchBody::GetBody;
+
+ void GetBody(nsIInputStream** aStream, int64_t* aBodyLength = nullptr) {
+ mRequest->GetBody(aStream, aBodyLength);
+ }
+
+ void SetBody(nsIInputStream* aStream, int64_t aBodyLength) {
+ mRequest->SetBody(aStream, aBodyLength);
+ }
+
+ using FetchBody::BodyBlobURISpec;
+
+ const nsACString& BodyBlobURISpec() const {
+ return mRequest->BodyBlobURISpec();
+ }
+
+ using FetchBody::BodyLocalPath;
+
+ const nsAString& BodyLocalPath() const { return mRequest->BodyLocalPath(); }
+
+ static SafeRefPtr<Request> Constructor(const GlobalObject& aGlobal,
+ const RequestOrUSVString& aInput,
+ const RequestInit& aInit,
+ ErrorResult& rv);
+
+ static SafeRefPtr<Request> Constructor(nsIGlobalObject* aGlobal,
+ JSContext* aCx,
+ const RequestOrUSVString& aInput,
+ const RequestInit& aInit,
+ ErrorResult& rv);
+
+ nsIGlobalObject* GetParentObject() const { return mOwner; }
+
+ SafeRefPtr<Request> Clone(ErrorResult& aRv);
+
+ SafeRefPtr<InternalRequest> GetInternalRequest();
+
+ const UniquePtr<mozilla::ipc::PrincipalInfo>& GetPrincipalInfo() const {
+ return mRequest->GetPrincipalInfo();
+ }
+
+ AbortSignal* GetOrCreateSignal();
+
+ // This can return a null AbortSignalImpl.
+ AbortSignalImpl* GetSignalImpl() const override;
+
+ private:
+ ~Request();
+
+ SafeRefPtr<InternalRequest> mRequest;
+
+ // Lazily created.
+ RefPtr<Headers> mHeaders;
+ RefPtr<AbortSignal> mSignal;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Request_h
diff --git a/dom/fetch/Response.cpp b/dom/fetch/Response.cpp
new file mode 100644
index 0000000000..bb2a937a27
--- /dev/null
+++ b/dom/fetch/Response.cpp
@@ -0,0 +1,498 @@
+/* -*- 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 "Response.h"
+
+#include "nsISupportsImpl.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/BodyStream.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/FetchBinding.h"
+#include "mozilla/dom/ResponseBinding.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/URL.h"
+#include "mozilla/dom/WorkerPrivate.h"
+
+#include "nsDOMString.h"
+
+#include "BodyExtractor.h"
+#include "FetchStreamReader.h"
+#include "InternalResponse.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ADDREF_INHERITED(Response, FetchBody<Response>)
+NS_IMPL_RELEASE_INHERITED(Response, FetchBody<Response>)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Response)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Response, FetchBody<Response>)
+ AbortFollower::Unlink(static_cast<AbortFollower*>(tmp));
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignalImpl)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader)
+
+ tmp->mReadableStreamBody = nullptr;
+ tmp->mReadableStreamReader = nullptr;
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Response, FetchBody<Response>)
+ AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignalImpl)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Response, FetchBody<Response>)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamBody)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(FetchBody<Response>)
+
+Response::Response(nsIGlobalObject* aGlobal,
+ InternalResponse* aInternalResponse,
+ AbortSignalImpl* aSignalImpl)
+ : FetchBody<Response>(aGlobal),
+ mInternalResponse(aInternalResponse),
+ mSignalImpl(aSignalImpl) {
+ MOZ_ASSERT(
+ aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable ||
+ aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response);
+
+ mozilla::HoldJSObjects(this);
+}
+
+Response::~Response() { mozilla::DropJSObjects(this); }
+
+/* static */
+already_AddRefed<Response> Response::Error(const GlobalObject& aGlobal) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<InternalResponse> error =
+ InternalResponse::NetworkError(NS_ERROR_FAILURE);
+ RefPtr<Response> r = new Response(global, error, nullptr);
+ return r.forget();
+}
+
+/* static */
+already_AddRefed<Response> Response::Redirect(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ uint16_t aStatus,
+ ErrorResult& aRv) {
+ nsAutoString parsedURL;
+
+ if (NS_IsMainThread()) {
+ nsIURI* baseURI = nullptr;
+ nsCOMPtr<nsPIDOMWindowInner> inner(
+ do_QueryInterface(aGlobal.GetAsSupports()));
+ Document* doc = inner ? inner->GetExtantDoc() : nullptr;
+ if (doc) {
+ baseURI = doc->GetBaseURI();
+ }
+ // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
+ nsAutoCString url;
+ if (!AppendUTF16toUTF8(aUrl, url, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(resolvedURI), url, nullptr, baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(url);
+ return nullptr;
+ }
+
+ nsAutoCString spec;
+ rv = resolvedURI->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.ThrowTypeError<MSG_INVALID_URL>(url);
+ return nullptr;
+ }
+
+ CopyUTF8toUTF16(spec, parsedURL);
+ } else {
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+
+ NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
+ RefPtr<URL> url =
+ URL::Constructor(aGlobal.GetAsSupports(), aUrl, baseURL, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ url->GetHref(parsedURL);
+ }
+
+ if (aStatus != 301 && aStatus != 302 && aStatus != 303 && aStatus != 307 &&
+ aStatus != 308) {
+ aRv.ThrowRangeError("Invalid redirect status code.");
+ return nullptr;
+ }
+
+ // We can't just pass nullptr for our null-valued Nullable, because the
+ // fetch::ResponseBodyInit is a non-temporary type due to the MOZ_RAII
+ // annotations on some of its members.
+ Nullable<fetch::ResponseBodyInit> body;
+ ResponseInit init;
+ init.mStatus = aStatus;
+ init.mStatusText.AssignASCII("");
+ RefPtr<Response> r = Response::Constructor(aGlobal, body, init, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ r->GetInternalHeaders()->Set("Location"_ns, NS_ConvertUTF16toUTF8(parsedURL),
+ aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ r->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable, aRv);
+ MOZ_ASSERT(!aRv.Failed());
+
+ return r.forget();
+}
+
+/*static*/
+already_AddRefed<Response> Response::Constructor(
+ const GlobalObject& aGlobal, const Nullable<fetch::ResponseBodyInit>& aBody,
+ const ResponseInit& aInit, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+
+ if (NS_WARN_IF(!global)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ if (aInit.mStatus < 200 || aInit.mStatus > 599) {
+ aRv.ThrowRangeError("Invalid response status code.");
+ return nullptr;
+ }
+
+ // Check if the status text contains illegal characters
+ nsACString::const_iterator start, end;
+ aInit.mStatusText.BeginReading(start);
+ aInit.mStatusText.EndReading(end);
+ if (FindCharInReadable('\r', start, end)) {
+ aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>();
+ return nullptr;
+ }
+ // Reset iterator since FindCharInReadable advances it.
+ aInit.mStatusText.BeginReading(start);
+ if (FindCharInReadable('\n', start, end)) {
+ aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>();
+ return nullptr;
+ }
+
+ RefPtr<InternalResponse> internalResponse =
+ new InternalResponse(aInit.mStatus, aInit.mStatusText);
+
+ UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo;
+
+ // Grab a valid channel info from the global so this response is 'valid' for
+ // interception.
+ if (NS_IsMainThread()) {
+ ChannelInfo info;
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
+ if (window) {
+ Document* doc = window->GetExtantDoc();
+ MOZ_ASSERT(doc);
+ info.InitFromDocument(doc);
+
+ principalInfo.reset(new mozilla::ipc::PrincipalInfo());
+ nsresult rv =
+ PrincipalToPrincipalInfo(doc->NodePrincipal(), principalInfo.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ internalResponse->InitChannelInfo(info);
+ } else if (global->PrincipalOrNull()->IsSystemPrincipal()) {
+ info.InitFromChromeGlobal(global);
+
+ internalResponse->InitChannelInfo(info);
+ }
+
+ /**
+ * The channel info is left uninitialized if neither the above `if` nor
+ * `else if` statements are executed; this could be because we're in a
+ * WebExtensions content script, where the global (i.e. `global`) is a
+ * wrapper, and the principal is an expanded principal. In this case,
+ * as far as I can tell, there's no way to get the security info, but we'd
+ * like the `Response` to be successfully constructed.
+ */
+ } else {
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ internalResponse->InitChannelInfo(worker->GetChannelInfo());
+ principalInfo =
+ MakeUnique<mozilla::ipc::PrincipalInfo>(worker->GetPrincipalInfo());
+ }
+
+ internalResponse->SetPrincipalInfo(std::move(principalInfo));
+
+ RefPtr<Response> r = new Response(global, internalResponse, nullptr);
+
+ if (aInit.mHeaders.WasPassed()) {
+ internalResponse->Headers()->Clear();
+
+ // Instead of using Fill, create an object to allow the constructor to
+ // unwrap the HeadersInit.
+ RefPtr<Headers> headers =
+ Headers::Create(global, aInit.mHeaders.Value(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ internalResponse->Headers()->Fill(*headers->GetInternalHeaders(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ if (!aBody.IsNull()) {
+ if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) {
+ aRv.ThrowTypeError("Response body is given with a null body status.");
+ return nullptr;
+ }
+
+ nsCString contentTypeWithCharset;
+ nsCOMPtr<nsIInputStream> bodyStream;
+ int64_t bodySize = InternalResponse::UNKNOWN_BODY_SIZE;
+
+ const fetch::ResponseBodyInit& body = aBody.Value();
+ if (body.IsReadableStream()) {
+ aRv.MightThrowJSException();
+
+ JSContext* cx = aGlobal.Context();
+ const ReadableStream& readableStream = body.GetAsReadableStream();
+
+ JS::Rooted<JSObject*> readableStreamObj(cx, readableStream.Obj());
+
+ bool disturbed;
+ bool locked;
+ if (!JS::ReadableStreamIsDisturbed(cx, readableStreamObj, &disturbed) ||
+ !JS::ReadableStreamIsLocked(cx, readableStreamObj, &locked)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return nullptr;
+ }
+ if (disturbed || locked) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ r->SetReadableStreamBody(cx, readableStreamObj);
+
+ JS::ReadableStreamMode streamMode;
+ if (!JS::ReadableStreamGetMode(cx, readableStreamObj, &streamMode)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return nullptr;
+ }
+ if (streamMode == JS::ReadableStreamMode::ExternalSource) {
+ // If this is a DOM generated ReadableStream, we can extract the
+ // inputStream directly.
+ JS::ReadableStreamUnderlyingSource* underlyingSource = nullptr;
+ if (!JS::ReadableStreamGetExternalUnderlyingSource(
+ cx, readableStreamObj, &underlyingSource)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(underlyingSource);
+
+ aRv = BodyStream::RetrieveInputStream(underlyingSource,
+ getter_AddRefs(bodyStream));
+
+ // The releasing of the external source is needed in order to avoid an
+ // extra stream lock.
+ if (!JS::ReadableStreamReleaseExternalUnderlyingSource(
+ cx, readableStreamObj)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return nullptr;
+ }
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ } else {
+ // If this is a JS-created ReadableStream, let's create a
+ // FetchStreamReader.
+ aRv = FetchStreamReader::Create(aGlobal.Context(), global,
+ getter_AddRefs(r->mFetchStreamReader),
+ getter_AddRefs(bodyStream));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+ } else {
+ uint64_t size = 0;
+ aRv = ExtractByteStreamFromBody(body, getter_AddRefs(bodyStream),
+ contentTypeWithCharset, size);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ bodySize = size;
+ }
+
+ internalResponse->SetBody(bodyStream, bodySize);
+
+ if (!contentTypeWithCharset.IsVoid() &&
+ !internalResponse->Headers()->Has("Content-Type"_ns, aRv)) {
+ // Ignore Append() failing here.
+ ErrorResult error;
+ internalResponse->Headers()->Append("Content-Type"_ns,
+ contentTypeWithCharset, error);
+ error.SuppressException();
+ }
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ return r.forget();
+}
+
+already_AddRefed<Response> Response::Clone(JSContext* aCx, ErrorResult& aRv) {
+ bool bodyUsed = GetBodyUsed(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (!bodyUsed && mReadableStreamBody) {
+ aRv.MightThrowJSException();
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mOwner)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> body(cx, mReadableStreamBody);
+ bool locked;
+ // We just need to check the 'locked' state because GetBodyUsed() already
+ // checked the 'disturbed' state.
+ if (!JS::ReadableStreamIsLocked(cx, body, &locked)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return nullptr;
+ }
+
+ bodyUsed = locked;
+ }
+
+ if (bodyUsed) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ RefPtr<FetchStreamReader> streamReader;
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ JS::Rooted<JSObject*> body(aCx);
+ MaybeTeeReadableStreamBody(aCx, &body, getter_AddRefs(streamReader),
+ getter_AddRefs(inputStream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT_IF(body, streamReader);
+ MOZ_ASSERT_IF(body, inputStream);
+
+ RefPtr<InternalResponse> ir =
+ mInternalResponse->Clone(body ? InternalResponse::eDontCloneInputStream
+ : InternalResponse::eCloneInputStream);
+
+ RefPtr<Response> response = new Response(mOwner, ir, GetSignalImpl());
+
+ if (body) {
+ // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
+ // if this body is a native stream. In this case the InternalResponse will
+ // have a clone of the native body and the ReadableStream will be created
+ // lazily if needed.
+ response->SetReadableStreamBody(aCx, body);
+ response->mFetchStreamReader = streamReader;
+ ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
+ }
+
+ return response.forget();
+}
+
+already_AddRefed<Response> Response::CloneUnfiltered(JSContext* aCx,
+ ErrorResult& aRv) {
+ if (GetBodyUsed(aRv)) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ RefPtr<FetchStreamReader> streamReader;
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ JS::Rooted<JSObject*> body(aCx);
+ MaybeTeeReadableStreamBody(aCx, &body, getter_AddRefs(streamReader),
+ getter_AddRefs(inputStream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT_IF(body, streamReader);
+ MOZ_ASSERT_IF(body, inputStream);
+
+ RefPtr<InternalResponse> clone =
+ mInternalResponse->Clone(body ? InternalResponse::eDontCloneInputStream
+ : InternalResponse::eCloneInputStream);
+
+ RefPtr<InternalResponse> ir = clone->Unfiltered();
+ RefPtr<Response> ref = new Response(mOwner, ir, GetSignalImpl());
+
+ if (body) {
+ // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
+ // if this body is a native stream. In this case the InternalResponse will
+ // have a clone of the native body and the ReadableStream will be created
+ // lazily if needed.
+ ref->SetReadableStreamBody(aCx, body);
+ ref->mFetchStreamReader = streamReader;
+ ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
+ }
+
+ return ref.forget();
+}
+
+void Response::SetBody(nsIInputStream* aBody, int64_t aBodySize) {
+ MOZ_ASSERT(!CheckBodyUsed());
+ mInternalResponse->SetBody(aBody, aBodySize);
+}
+
+already_AddRefed<InternalResponse> Response::GetInternalResponse() const {
+ RefPtr<InternalResponse> ref = mInternalResponse;
+ return ref.forget();
+}
+
+Headers* Response::Headers_() {
+ if (!mHeaders) {
+ mHeaders = new Headers(mOwner, mInternalResponse->Headers());
+ }
+
+ return mHeaders;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/Response.h b/dom/fetch/Response.h
new file mode 100644
index 0000000000..adbe5bb164
--- /dev/null
+++ b/dom/fetch/Response.h
@@ -0,0 +1,136 @@
+/* -*- 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_Response_h
+#define mozilla_dom_Response_h
+
+#include "nsWrapperCache.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/dom/Fetch.h"
+#include "mozilla/dom/ResponseBinding.h"
+
+#include "InternalHeaders.h"
+#include "InternalResponse.h"
+
+namespace mozilla {
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class Headers;
+
+class Response final : public FetchBody<Response>, public nsWrapperCache {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(Response,
+ FetchBody<Response>)
+
+ public:
+ Response(nsIGlobalObject* aGlobal, InternalResponse* aInternalResponse,
+ AbortSignalImpl* aSignalImpl);
+
+ Response(const Response& aOther) = delete;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return Response_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ ResponseType Type() const { return mInternalResponse->Type(); }
+ void GetUrl(nsAString& aUrl) const {
+ CopyUTF8toUTF16(mInternalResponse->GetURL(), aUrl);
+ }
+ bool Redirected() const { return mInternalResponse->IsRedirected(); }
+ uint16_t Status() const { return mInternalResponse->GetStatus(); }
+
+ bool Ok() const {
+ return mInternalResponse->GetStatus() >= 200 &&
+ mInternalResponse->GetStatus() <= 299;
+ }
+
+ void GetStatusText(nsCString& aStatusText) const {
+ aStatusText = mInternalResponse->GetStatusText();
+ }
+
+ InternalHeaders* GetInternalHeaders() const {
+ return mInternalResponse->Headers();
+ }
+
+ void InitChannelInfo(nsIChannel* aChannel) {
+ mInternalResponse->InitChannelInfo(aChannel);
+ }
+
+ const ChannelInfo& GetChannelInfo() const {
+ return mInternalResponse->GetChannelInfo();
+ }
+
+ const UniquePtr<mozilla::ipc::PrincipalInfo>& GetPrincipalInfo() const {
+ return mInternalResponse->GetPrincipalInfo();
+ }
+
+ bool HasCacheInfoChannel() const {
+ return mInternalResponse->HasCacheInfoChannel();
+ }
+
+ Headers* Headers_();
+
+ void GetBody(nsIInputStream** aStream, int64_t* aBodyLength = nullptr) {
+ mInternalResponse->GetBody(aStream, aBodyLength);
+ }
+
+ using FetchBody::GetBody;
+
+ using FetchBody::BodyBlobURISpec;
+
+ const nsACString& BodyBlobURISpec() const {
+ return mInternalResponse->BodyBlobURISpec();
+ }
+
+ using FetchBody::BodyLocalPath;
+
+ const nsAString& BodyLocalPath() const {
+ return mInternalResponse->BodyLocalPath();
+ }
+
+ static already_AddRefed<Response> Error(const GlobalObject& aGlobal);
+
+ static already_AddRefed<Response> Redirect(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ uint16_t aStatus,
+ ErrorResult& aRv);
+
+ static already_AddRefed<Response> Constructor(
+ const GlobalObject& aGlobal,
+ const Nullable<fetch::ResponseBodyInit>& aBody, const ResponseInit& aInit,
+ ErrorResult& rv);
+
+ nsIGlobalObject* GetParentObject() const { return mOwner; }
+
+ already_AddRefed<Response> Clone(JSContext* aCx, ErrorResult& aRv);
+
+ already_AddRefed<Response> CloneUnfiltered(JSContext* aCx, ErrorResult& aRv);
+
+ void SetBody(nsIInputStream* aBody, int64_t aBodySize);
+
+ already_AddRefed<InternalResponse> GetInternalResponse() const;
+
+ AbortSignalImpl* GetSignalImpl() const override { return mSignalImpl; }
+
+ private:
+ ~Response();
+
+ RefPtr<InternalResponse> mInternalResponse;
+ // Lazily created
+ RefPtr<Headers> mHeaders;
+ RefPtr<AbortSignalImpl> mSignalImpl;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Response_h
diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build
new file mode 100644
index 0000000000..d6be68726a
--- /dev/null
+++ b/dom/fetch/moz.build
@@ -0,0 +1,65 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Networking")
+
+EXPORTS.mozilla.dom += [
+ "BodyExtractor.h",
+ "ChannelInfo.h",
+ "Fetch.h",
+ "FetchDriver.h",
+ "FetchIPCTypes.h",
+ "FetchObserver.h",
+ "FetchStreamReader.h",
+ "FetchUtil.h",
+ "Headers.h",
+ "InternalHeaders.h",
+ "InternalRequest.h",
+ "InternalResponse.h",
+ "Request.h",
+ "Response.h",
+]
+
+UNIFIED_SOURCES += [
+ "BodyExtractor.cpp",
+ "ChannelInfo.cpp",
+ "EmptyBody.cpp",
+ "Fetch.cpp",
+ "FetchDriver.cpp",
+ "FetchObserver.cpp",
+ "FetchStreamReader.cpp",
+ "FetchUtil.cpp",
+ "Headers.cpp",
+ "InternalHeaders.cpp",
+ "InternalRequest.cpp",
+ "InternalResponse.cpp",
+ "Request.cpp",
+ "Response.cpp",
+]
+
+IPDL_SOURCES += [
+ "ChannelInfo.ipdlh",
+ "FetchTypes.ipdlh",
+]
+
+LOCAL_INCLUDES += [
+ # For nsDOMSerializer
+ "/dom/base",
+ # For HttpBaseChannel.h dependencies
+ "/netwerk/base",
+ # For nsDataHandler.h
+ "/netwerk/protocol/data",
+ # For HttpBaseChannel.h
+ "/netwerk/protocol/http",
+]
+
+BROWSER_CHROME_MANIFESTS += ["tests/browser.ini"]
+MOCHITEST_MANIFESTS += ["tests/mochitest.ini"]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/dom/fetch/tests/.eslintrc.js b/dom/fetch/tests/.eslintrc.js
new file mode 100644
index 0000000000..1779fd7f1c
--- /dev/null
+++ b/dom/fetch/tests/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/browser-test"],
+};
diff --git a/dom/fetch/tests/browser.ini b/dom/fetch/tests/browser.ini
new file mode 100644
index 0000000000..8705dea1ab
--- /dev/null
+++ b/dom/fetch/tests/browser.ini
@@ -0,0 +1,2 @@
+[DEFAULT]
+[browser_blobFromFile.js]
diff --git a/dom/fetch/tests/browser_blobFromFile.js b/dom/fetch/tests/browser_blobFromFile.js
new file mode 100644
index 0000000000..155f420477
--- /dev/null
+++ b/dom/fetch/tests/browser_blobFromFile.js
@@ -0,0 +1,62 @@
+add_task(async function test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.tabs.remote.separateFileUriProcess", true]],
+ });
+
+ let fileData = "";
+ for (var i = 0; i < 100; ++i) {
+ fileData += "hello world!";
+ }
+
+ let file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ file.append("file.txt");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ let outStream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ outStream.init(
+ file,
+ 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666,
+ 0
+ );
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ let fileHandler = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .getProtocolHandler("file")
+ .QueryInterface(Ci.nsIFileProtocolHandler);
+
+ let fileURL = fileHandler.getURLSpecFromFile(file);
+
+ info("Opening url: " + fileURL);
+ let tab = BrowserTestUtils.addTab(gBrowser, fileURL);
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let blob = await SpecialPowers.spawn(browser, [file.leafName], function(
+ fileName
+ ) {
+ return new content.window.Promise(resolve => {
+ content.window
+ .fetch(fileName)
+ .then(r => r.blob())
+ .then(blob => resolve(blob));
+ });
+ });
+
+ ok(blob instanceof File, "We have a file");
+
+ is(blob.size, file.fileSize, "The size matches");
+ is(blob.name, file.leafName, "The name is correct");
+
+ file.remove(false);
+
+ gBrowser.removeTab(tab);
+});
diff --git a/dom/fetch/tests/crashtests/1577196.html b/dom/fetch/tests/crashtests/1577196.html
new file mode 100644
index 0000000000..a2cdae06d0
--- /dev/null
+++ b/dom/fetch/tests/crashtests/1577196.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+
+function test() {
+ const xhr = new XMLHttpRequest();
+ const frame = document.createElement("frame");
+ document.documentElement.appendChild(frame);
+ const win = frame.contentWindow;
+ const div = document.createElement("div");
+ div.insertBefore(frame, div.childNodes[0]);
+ SpecialPowers.forceCC();
+ SpecialPowers.gc();
+ xhr.open("P", "", false);
+ xhr.send();
+ win.fetch(null, {}).then(function(arg14) {});
+}
+
+for (let i = 0; i < 10; i++) {
+ test();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/fetch/tests/crashtests/1664514.html b/dom/fetch/tests/crashtests/1664514.html
new file mode 100644
index 0000000000..dae325d2a7
--- /dev/null
+++ b/dom/fetch/tests/crashtests/1664514.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<h3>Set <code>privacy.file_unique_origin</code> to false when testing this.</h3>
+<script>
+ fetch("./url.url")
+</script>
diff --git a/dom/fetch/tests/crashtests/crashtests.list b/dom/fetch/tests/crashtests/crashtests.list
new file mode 100644
index 0000000000..b3210cd4d8
--- /dev/null
+++ b/dom/fetch/tests/crashtests/crashtests.list
@@ -0,0 +1,2 @@
+load 1577196.html
+pref(privacy.file_unique_origin,false) load 1664514.html
diff --git a/dom/fetch/tests/crashtests/url.url b/dom/fetch/tests/crashtests/url.url
new file mode 100644
index 0000000000..95e9cf8db0
--- /dev/null
+++ b/dom/fetch/tests/crashtests/url.url
@@ -0,0 +1,5 @@
+[{000214A0-0000-0000-C000-000000000046}]
+Prop3=19,11
+[InternetShortcut]
+IDList=
+URL=http://localhost:8000/
diff --git a/dom/fetch/tests/mochitest.ini b/dom/fetch/tests/mochitest.ini
new file mode 100644
index 0000000000..32eb841faf
--- /dev/null
+++ b/dom/fetch/tests/mochitest.ini
@@ -0,0 +1,2 @@
+[test_ext_response_constructor.html]
+[test_invalid_header_exception.html]
diff --git a/dom/fetch/tests/test_ext_response_constructor.html b/dom/fetch/tests/test_ext_response_constructor.html
new file mode 100644
index 0000000000..adff6c78e9
--- /dev/null
+++ b/dom/fetch/tests/test_ext_response_constructor.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test `Response` constructor in a WebExtension</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ add_task(async function testResponseConstructor() {
+ function contentScript() {
+ new Response();
+ browser.test.notifyPass("done");
+ }
+
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ content_scripts: [
+ {
+ matches: ["<all_urls>"],
+ js: ["content_script.js"],
+ },
+ ],
+ },
+
+ files: {
+ "content_script.js": contentScript,
+ },
+ });
+
+ await extension.startup();
+
+ const win = window.open("https://example.com");
+ await extension.awaitFinish("done");
+ win.close();
+
+ await extension.unload();
+ });
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/fetch/tests/test_invalid_header_exception.html b/dom/fetch/tests/test_invalid_header_exception.html
new file mode 100644
index 0000000000..274e85bb68
--- /dev/null
+++ b/dom/fetch/tests/test_invalid_header_exception.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1629390
+-->
+<head>
+ <title>Test for Bug 1629390</title>
+ <meta charset="UTF-8">
+ <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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1629390">Mozilla Bug 1629390</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1629390 **/
+let headerNames = [
+ ['a˻b', 'a\uFFFD\uFFFDb'],
+ ['Àaª', '\uFFFDa\uFFFD'],
+ ['˻b', '\uFFFD\uFFFDb'],
+ ['\xEAa', '\uFFFDa'],
+ ['\xC2\x7F', '\uFFFD\x7F'],
+];
+for ([invalid, corrected] of headerNames) {
+ try {
+ new Headers([[invalid, '']]);
+ } catch(e) {
+ is(e.message, `Headers constructor: ${corrected} is an invalid header name.`);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>