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.h42
-rw-r--r--dom/fetch/ChannelInfo.cpp95
-rw-r--r--dom/fetch/ChannelInfo.h70
-rw-r--r--dom/fetch/Fetch.cpp1683
-rw-r--r--dom/fetch/Fetch.h310
-rw-r--r--dom/fetch/FetchChild.cpp380
-rw-r--r--dom/fetch/FetchChild.h91
-rw-r--r--dom/fetch/FetchDriver.cpp1755
-rw-r--r--dom/fetch/FetchDriver.h232
-rw-r--r--dom/fetch/FetchIPCTypes.h63
-rw-r--r--dom/fetch/FetchLog.h19
-rw-r--r--dom/fetch/FetchObserver.cpp79
-rw-r--r--dom/fetch/FetchObserver.h45
-rw-r--r--dom/fetch/FetchParent.cpp340
-rw-r--r--dom/fetch/FetchParent.h109
-rw-r--r--dom/fetch/FetchService.cpp643
-rw-r--r--dom/fetch/FetchService.h186
-rw-r--r--dom/fetch/FetchStreamReader.cpp441
-rw-r--r--dom/fetch/FetchStreamReader.h94
-rw-r--r--dom/fetch/FetchStreamUtils.cpp83
-rw-r--r--dom/fetch/FetchStreamUtils.h56
-rw-r--r--dom/fetch/FetchTypes.ipdlh127
-rw-r--r--dom/fetch/FetchUtil.cpp814
-rw-r--r--dom/fetch/FetchUtil.h82
-rw-r--r--dom/fetch/Headers.cpp87
-rw-r--r--dom/fetch/Headers.h132
-rw-r--r--dom/fetch/InternalHeaders.cpp654
-rw-r--r--dom/fetch/InternalHeaders.h180
-rw-r--r--dom/fetch/InternalRequest.cpp478
-rw-r--r--dom/fetch/InternalRequest.h489
-rw-r--r--dom/fetch/InternalResponse.cpp458
-rw-r--r--dom/fetch/InternalResponse.h419
-rw-r--r--dom/fetch/PFetch.ipdl56
-rw-r--r--dom/fetch/Request.cpp658
-rw-r--r--dom/fetch/Request.h142
-rw-r--r--dom/fetch/Response.cpp477
-rw-r--r--dom/fetch/Response.h161
-rw-r--r--dom/fetch/moz.build72
-rw-r--r--dom/fetch/tests/browser.ini23
-rw-r--r--dom/fetch/tests/browser_blobFromFile.js64
-rw-r--r--dom/fetch/tests/browser_origin_trial_coep_credentialless_cache.js139
-rw-r--r--dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_1.js145
-rw-r--r--dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_2.js145
-rw-r--r--dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_3.js150
-rw-r--r--dom/fetch/tests/browser_origin_trial_coep_credentialless_worker.js164
-rw-r--r--dom/fetch/tests/crashtests/1577196.html26
-rw-r--r--dom/fetch/tests/crashtests/1664514.html5
-rw-r--r--dom/fetch/tests/crashtests/crashtests.list2
-rw-r--r--dom/fetch/tests/crashtests/url.url5
-rw-r--r--dom/fetch/tests/credentialless_resource.sjs21
-rw-r--r--dom/fetch/tests/credentialless_worker.sjs25
-rw-r--r--dom/fetch/tests/mochitest.ini2
-rw-r--r--dom/fetch/tests/open_credentialless_document.sjs28
-rw-r--r--dom/fetch/tests/store_header.sjs23
-rw-r--r--dom/fetch/tests/test_ext_response_constructor.html47
-rw-r--r--dom/fetch/tests/test_invalid_header_exception.html39
57 files changed, 13537 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..08e5210afc
--- /dev/null
+++ b/dom/fetch/BodyExtractor.h
@@ -0,0 +1,42 @@
+/* -*- 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::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 mozilla::dom
+
+#endif // mozilla_dom_BodyExtractor_h
diff --git a/dom/fetch/ChannelInfo.cpp b/dom/fetch/ChannelInfo.cpp
new file mode 100644
index 0000000000..71b9e3277b
--- /dev/null
+++ b/dom/fetch/ChannelInfo.cpp
@@ -0,0 +1,95 @@
+/* -*- 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 "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<nsITransportSecurityInfo> 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<nsITransportSecurityInfo> 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 = nullptr;
+ mInited = true;
+}
+
+void ChannelInfo::InitFromTransportSecurityInfo(
+ nsITransportSecurityInfo* aSecurityInfo) {
+ MOZ_ASSERT(!mInited, "Cannot initialize the object twice");
+
+ mSecurityInfo = aSecurityInfo;
+ mInited = true;
+}
+
+void ChannelInfo::SetSecurityInfo(nsITransportSecurityInfo* aSecurityInfo) {
+ MOZ_ASSERT(!mSecurityInfo, "security info should only be set once");
+ mSecurityInfo = aSecurityInfo;
+}
+
+nsresult ChannelInfo::ResurrectInfoOnChannel(nsIChannel* aChannel) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mInited);
+
+ if (mSecurityInfo) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ MOZ_ASSERT(httpChannel);
+ net::HttpBaseChannel* httpBaseChannel =
+ static_cast<net::HttpBaseChannel*>(httpChannel.get());
+ nsresult rv = httpBaseChannel->OverrideSecurityInfo(mSecurityInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsITransportSecurityInfo> ChannelInfo::SecurityInfo() 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.
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(mSecurityInfo);
+ return securityInfo.forget();
+}
diff --git a/dom/fetch/ChannelInfo.h b/dom/fetch/ChannelInfo.h
new file mode 100644
index 0000000000..7df6c53ec4
--- /dev/null
+++ b/dom/fetch/ChannelInfo.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_ChannelInfo_h
+#define mozilla_dom_ChannelInfo_h
+
+#include "nsCOMPtr.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsString.h"
+
+class nsIChannel;
+class nsIGlobalObject;
+class nsIURI;
+
+namespace mozilla {
+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:
+// * InitFromChannel and InitFromTransportSecurityInfo members
+// * ResurrectInfoOnChannel member
+// * SecurityInfo 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:
+ 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 InitFromTransportSecurityInfo(nsITransportSecurityInfo* aSecurityInfo);
+
+ // This restores every possible information stored from a previous channel
+ // object on a new one.
+ nsresult ResurrectInfoOnChannel(nsIChannel* aChannel);
+
+ bool IsInitialized() const { return mInited; }
+
+ already_AddRefed<nsITransportSecurityInfo> SecurityInfo() const;
+
+ private:
+ void SetSecurityInfo(nsITransportSecurityInfo* aSecurityInfo);
+
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ bool mInited;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ChannelInfo_h
diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp
new file mode 100644
index 0000000000..c4589bd513
--- /dev/null
+++ b/dom/fetch/Fetch.cpp
@@ -0,0 +1,1683 @@
+/* -*- 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 "js/RootingAPI.h"
+#include "js/Value.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/ipc/IPCStreamUtils.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/ReadableStreamDefaultReader.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 "FetchChild.h"
+#include "FetchObserver.h"
+#include "InternalRequest.h"
+#include "InternalResponse.h"
+
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerScope.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+// Step 17.2.1.2 and 17.2.2 of
+// https://fetch.spec.whatwg.org/#concept-http-network-fetch
+// If stream is readable, then error stream with ...
+void AbortStream(JSContext* aCx, ReadableStream* aReadableStream,
+ ErrorResult& aRv, JS::Handle<JS::Value> aReasonDetails) {
+ if (aReadableStream->State() != ReadableStream::ReaderState::Readable) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> value(aCx, aReasonDetails);
+
+ if (aReasonDetails.isUndefined()) {
+ RefPtr<DOMException> e = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
+ if (!GetOrCreateDOMReflector(aCx, e, &value)) {
+ return;
+ }
+ }
+
+ aReadableStream->ErrorNative(aCx, value, aRv);
+}
+
+} // namespace
+
+class AbortSignalMainThread final : public AbortSignalImpl {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbortSignalMainThread)
+
+ explicit AbortSignalMainThread(bool aAborted)
+ : AbortSignalImpl(aAborted, JS::UndefinedHandleValue) {
+ mozilla::HoldJSObjects(this);
+ }
+
+ private:
+ ~AbortSignalMainThread() { mozilla::DropJSObjects(this); };
+};
+
+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_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbortSignalMainThread)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReason)
+NS_IMPL_CYCLE_COLLECTION_TRACE_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;
+
+// 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::AbortSignalProxyRunnable"), mProxy(aProxy) {}
+
+ NS_IMETHOD Run() override;
+};
+
+// 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 AbortSignalProxyRunnable::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+ AbortSignalImpl* signalImpl = mProxy->GetOrCreateSignalImplForMainThread();
+ signalImpl->SignalAbort(JS::UndefinedHandleValue);
+ return NS_OK;
+}
+
+void AbortSignalProxy::RunAbortAlgorithm() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ 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;
+
+ Atomic<bool> mNeedOnDataAvailable;
+
+ 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(
+ SafeRefPtr<InternalResponse> aResponse) override;
+
+ void OnResponseEnd(FetchDriverObserver::EndReason aReason,
+ JS::Handle<JS::Value> aReasonDetails) override;
+
+ bool NeedOnDataAvailable() override;
+
+ void OnDataAvailable() override;
+
+ void Shutdown(WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ mIsShutdown = true;
+ mPromiseProxy->CleanUp();
+
+ mNeedOnDataAvailable = false;
+ 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),
+ mNeedOnDataAvailable(!!aObserver) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mPromiseProxy);
+ }
+
+ ~WorkerFetchResolver() = default;
+
+ virtual void FlushConsoleReport() override;
+};
+
+void FetchDriverObserver::OnResponseAvailable(
+ SafeRefPtr<InternalResponse> aResponse) {
+ MOZ_ASSERT(!mGotResponseAvailable);
+ mGotResponseAvailable = true;
+ OnResponseAvailableInternal(std::move(aResponse));
+}
+
+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(
+ SafeRefPtr<InternalResponse> aResponse) override;
+
+ void SetLoadGroup(nsILoadGroup* aLoadGroup) { mLoadGroup = aLoadGroup; }
+
+ void OnResponseEnd(FetchDriverObserver::EndReason aReason,
+ JS::Handle<JS::Value> aReasonDetails) override {
+ if (aReason == eAborted) {
+ if (!aReasonDetails.isUndefined()) {
+ mPromise->MaybeReject(aReasonDetails);
+ } else {
+ 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();
+
+ // Restore information of InterceptedHttpChannel if they are passed with the
+ // Request. Since Request::Constructor would not copy these members.
+ if (aInput.IsRequest()) {
+ RefPtr<Request> inputReq = &aInput.GetAsRequest();
+ SafeRefPtr<InternalRequest> inputInReq = inputReq->GetInternalRequest();
+ if (inputInReq->GetInterceptionTriggeringPrincipalInfo()) {
+ r->SetInterceptionContentPolicyType(
+ inputInReq->InterceptionContentPolicyType());
+ r->SetInterceptionTriggeringPrincipalInfo(
+ MakeUnique<mozilla::ipc::PrincipalInfo>(
+ *(inputInReq->GetInterceptionTriggeringPrincipalInfo().get())));
+ if (!inputInReq->InterceptionRedirectChain().IsEmpty()) {
+ r->SetInterceptionRedirectChain(
+ inputInReq->InterceptionRedirectChain());
+ }
+ r->SetInterceptionFromThirdParty(
+ inputInReq->InterceptionFromThirdParty());
+ }
+ }
+
+ RefPtr<AbortSignalImpl> signalImpl = request->GetSignalImpl();
+
+ if (signalImpl && signalImpl->Aborted()) {
+ // Already aborted signal rejects immediately.
+ JS::Rooted<JS::Value> reason(cx, signalImpl->RawReason());
+ if (reason.get().isUndefined()) {
+ aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+ return nullptr;
+ }
+
+ p->MaybeReject(reason);
+ return p.forget();
+ }
+
+ JS::Realm* realm = JS::GetCurrentRealmOrNull(cx);
+ if (realm && JS::GetDebuggerObservesWasm(realm)) {
+ r->SetSkipWasmCaching();
+ }
+
+ 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(principal);
+ }
+
+ 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();
+ }
+
+ // PFetch gives no benefit for the fetch in the parent process.
+ // Dispatch fetch to the parent process main thread directly for that case.
+ // For child process, dispatch fetch op to the parent.
+ if (StaticPrefs::dom_workers_pFetch_enabled() && !XRE_IsParentProcess()) {
+ RefPtr<FetchChild> actor =
+ FetchChild::Create(worker, p, signalImpl, observer);
+ if (!actor) {
+ 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;
+ }
+
+ auto* backgroundChild =
+ mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ Unused << NS_WARN_IF(!backgroundChild->SendPFetchConstructor(actor));
+
+ FetchOpArgs ipcArgs;
+
+ ipcArgs.request() = IPCInternalRequest();
+ r->ToIPCInternalRequest(&(ipcArgs.request()), backgroundChild);
+
+ ipcArgs.principalInfo() = worker->GetPrincipalInfo();
+ ipcArgs.clientInfo() = clientInfo.ref().ToIPC();
+ if (worker->GetBaseURI()) {
+ worker->GetBaseURI()->GetAsciiSpec(ipcArgs.workerScript());
+ }
+ if (worker->GlobalScope()->GetController().isSome()) {
+ ipcArgs.controller() =
+ Some(worker->GlobalScope()->GetController().ref().ToIPC());
+ }
+ if (worker->CookieJarSettings()) {
+ ipcArgs.cookieJarSettings() = Some(worker->CookieJarSettingsArgs());
+ }
+ if (worker->CSPEventListener()) {
+ ipcArgs.hasCSPEventListener() = true;
+ actor->SetCSPEventListener(worker->CSPEventListener());
+ } else {
+ ipcArgs.hasCSPEventListener() = false;
+ }
+
+ ipcArgs.associatedBrowsingContextID() =
+ worker->AssociatedBrowsingContextID();
+
+ if (worker->IsWatchedByDevTools()) {
+ UniquePtr<SerializedStackHolder> stack;
+ stack = GetCurrentStackForNetMonitor(cx);
+ actor->SetOriginStack(std::move(stack));
+ }
+
+ actor->DoFetchOp(ipcArgs);
+
+ return p.forget();
+ }
+
+ 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() override {
+ mPromise->MaybeResolve(mResponse);
+ return NS_OK;
+ }
+ RefPtr<Promise> mPromise;
+ RefPtr<Response> mResponse;
+};
+
+void MainThreadFetchResolver::OnResponseAvailableInternal(
+ SafeRefPtr<InternalResponse> aResponse) {
+ NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver);
+ AssertIsOnMainThread();
+
+ if (aResponse->Type() != ResponseType::Error) {
+ nsCOMPtr<nsIGlobalObject> go = mPromise->GetParentObject();
+ nsCOMPtr<nsPIDOMWindowInner> inner = do_QueryInterface(go);
+
+ // Notify the document when a fetch completes successfully. This is
+ // used by the password manager as a hint to observe DOM mutations.
+ // Call this prior to setting state to Complete so we can set up the
+ // observer before mutations occurs.
+ Document* doc = inner ? inner->GetExtantDoc() : nullptr;
+ if (doc) {
+ doc->NotifyFetchOrXHRSuccess();
+ }
+
+ if (mFetchObserver) {
+ mFetchObserver->SetState(FetchState::Complete);
+ }
+
+ mResponse = new Response(go, std::move(aResponse), mSignalImpl);
+ // response headers received from the network should be immutable
+ // all response header settings must be done before this point
+ // see Bug 1574174
+ ErrorResult result;
+ mResponse->Headers_()->SetGuard(HeadersGuardEnum::Immutable, result);
+ MOZ_ASSERT(!result.Failed());
+
+ 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.
+ SafeRefPtr<InternalResponse> mInternalResponse;
+
+ public:
+ WorkerFetchResponseRunnable(WorkerPrivate* aWorkerPrivate,
+ WorkerFetchResolver* aResolver,
+ SafeRefPtr<InternalResponse> aResponse)
+ : MainThreadWorkerRunnable(aWorkerPrivate),
+ mResolver(aResolver),
+ mInternalResponse(std::move(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.clonePtr(),
+ mResolver->GetAbortSignalForTargetThread());
+
+ // response headers received from the network should be immutable,
+ // all response header settings must be done before this point
+ // see Bug 1574174
+ ErrorResult result;
+ response->Headers_()->SetGuard(HeadersGuardEnum::Immutable, result);
+ MOZ_ASSERT(!result.Failed());
+
+ 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 {
+ // We need to check first if cancel is called twice
+ nsresult rv = WorkerRunnable::Cancel();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Execute Run anyway to make sure we cleanup our promise proxy to avoid
+ // leaking the worker thread
+ Run();
+ return NS_OK;
+ }
+};
+
+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(
+ SafeRefPtr<InternalResponse> aResponse) {
+ AssertIsOnMainThread();
+
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return;
+ }
+
+ RefPtr<WorkerFetchResponseRunnable> r = new WorkerFetchResponseRunnable(
+ mPromiseProxy->GetWorkerPrivate(), this, std::move(aResponse));
+
+ if (!r->Dispatch()) {
+ NS_WARNING("Could not dispatch fetch response");
+ }
+}
+
+bool WorkerFetchResolver::NeedOnDataAvailable() {
+ AssertIsOnMainThread();
+ return mNeedOnDataAvailable;
+}
+
+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,
+ JS::Handle<JS::Value> aReasonDetails) {
+ AssertIsOnMainThread();
+ MutexAutoLock lock(mPromiseProxy->Lock());
+ if (mPromiseProxy->CleanedUp()) {
+ return;
+ }
+
+ FlushConsoleReport();
+
+ Unused << aReasonDetails;
+
+ 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;
+}
+
+NS_IMPL_CYCLE_COLLECTION(FetchBodyBase, mReadableStreamBody)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FetchBodyBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FetchBodyBase)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchBodyBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+template <class Derived>
+FetchBody<Derived>::FetchBody(nsIGlobalObject* aOwner)
+ : mOwner(aOwner), mBodyUsed(false) {
+ MOZ_ASSERT(aOwner);
+
+ if (!NS_IsMainThread()) {
+ WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(wp);
+ mMainThreadEventTarget = wp->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>::BodyUsed() const {
+ if (mBodyUsed) {
+ return true;
+ }
+
+ // If this stream is disturbed, return true.
+ if (mReadableStreamBody) {
+ return mReadableStreamBody->Disturbed();
+ }
+
+ return false;
+}
+
+template bool FetchBody<Request>::BodyUsed() const;
+
+template bool FetchBody<Response>::BodyUsed() const;
+
+template <class Derived>
+void FetchBody<Derived>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(mOwner->EventTargetFor(TaskCategory::Other)->IsOnCurrentThread());
+
+ MOZ_DIAGNOSTIC_ASSERT(!BodyUsed(), "Consuming already used body?");
+ if (BodyUsed()) {
+ 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) {
+ if (mReadableStreamBody->MaybeGetInputStreamIfUnread()) {
+ LockStream(aCx, mReadableStreamBody, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ } else {
+ MOZ_ASSERT(mFetchStreamReader);
+ // Let's activate the FetchStreamReader.
+ mFetchStreamReader->StartConsuming(aCx, mReadableStreamBody, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+ }
+}
+
+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()->GetSignalImplToConsumeBody();
+
+ if (signalImpl && signalImpl->Aborted()) {
+ JS::Rooted<JS::Value> abortReason(aCx, signalImpl->RawReason());
+
+ if (abortReason.get().isUndefined()) {
+ aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> go = DerivedClass()->GetParentObject();
+
+ RefPtr<Promise> promise = Promise::Create(go, aRv);
+ promise->MaybeReject(abortReason);
+ return promise.forget();
+ }
+
+ if (BodyUsed()) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ nsAutoCString mimeType;
+ nsAutoCString mixedCaseMimeType;
+ DerivedClass()->GetMimeType(mimeType, mixedCaseMimeType);
+
+ // 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, mixedCaseMimeType, 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, mixedCaseMimeType,
+ 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,
+ nsACString& aMixedCaseMimeType) {
+ // 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);
+ aMixedCaseMimeType = aMimeType;
+ ToLowerCase(aMimeType);
+ }
+}
+
+template void FetchBody<Request>::GetMimeType(nsACString& aMimeType,
+ nsACString& aMixedCaseMimeType);
+template void FetchBody<Response>::GetMimeType(nsACString& aMimeType,
+ nsACString& aMixedCaseMimeType);
+
+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,
+ ReadableStream* aBody) {
+ MOZ_ASSERT(!mReadableStreamBody);
+ MOZ_ASSERT(aBody);
+ mReadableStreamBody = aBody;
+
+ RefPtr<AbortSignalImpl> signalImpl = DerivedClass()->GetSignalImpl();
+ if (!signalImpl) {
+ return;
+ }
+
+ bool aborted = signalImpl->Aborted();
+ if (aborted) {
+ IgnoredErrorResult result;
+ JS::Rooted<JS::Value> abortReason(aCx, signalImpl->RawReason());
+ AbortStream(aCx, mReadableStreamBody, result, abortReason);
+ if (NS_WARN_IF(result.Failed())) {
+ return;
+ }
+ } else if (!IsFollowing()) {
+ Follow(signalImpl);
+ }
+}
+
+template void FetchBody<Request>::SetReadableStreamBody(JSContext* aCx,
+ ReadableStream* aBody);
+
+template void FetchBody<Response>::SetReadableStreamBody(JSContext* aCx,
+ ReadableStream* aBody);
+
+template <class Derived>
+already_AddRefed<ReadableStream> FetchBody<Derived>::GetBody(JSContext* aCx,
+ ErrorResult& aRv) {
+ if (mReadableStreamBody) {
+ return do_AddRef(mReadableStreamBody);
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ DerivedClass()->GetBody(getter_AddRefs(inputStream));
+
+ if (!inputStream) {
+ return nullptr;
+ }
+
+ // The spec immediately creates ReadableStream on Response/Request constructor
+ // via https://fetch.spec.whatwg.org/#concept-bodyinit-extract, but Gecko
+ // creates nsIInputStream there instead and creates ReadableStream only when
+ // .body is accessed. Thus we only follow step 4 of it here.
+ //
+ // Step 4: Otherwise, set stream to a new ReadableStream object, and set up
+ // stream with byte reading support.
+ auto algorithms =
+ MakeRefPtr<NonAsyncInputToReadableStreamAlgorithms>(*inputStream);
+ RefPtr<ReadableStream> body = ReadableStream::CreateByteNative(
+ aCx, DerivedClass()->GetParentObject(), *algorithms, Nothing(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ mReadableStreamBody = body;
+
+ // If the body has been already consumed, we lock the stream.
+ if (BodyUsed()) {
+ LockStream(aCx, body, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ RefPtr<AbortSignalImpl> signalImpl = DerivedClass()->GetSignalImpl();
+ if (signalImpl) {
+ if (signalImpl->Aborted()) {
+ JS::Rooted<JS::Value> abortReason(aCx, signalImpl->RawReason());
+ AbortStream(aCx, body, aRv, abortReason);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ } else if (!IsFollowing()) {
+ Follow(signalImpl);
+ }
+ }
+
+ return body.forget();
+}
+
+template already_AddRefed<ReadableStream> FetchBody<Request>::GetBody(
+ JSContext* aCx, ErrorResult& aRv);
+
+template already_AddRefed<ReadableStream> FetchBody<Response>::GetBody(
+ JSContext* aCx, ErrorResult& aRv);
+
+template <class Derived>
+void FetchBody<Derived>::LockStream(JSContext* aCx, ReadableStream* aStream,
+ ErrorResult& aRv) {
+ // This is native stream, creating a reader will not execute any JS code.
+ RefPtr<ReadableStreamDefaultReader> reader = aStream->GetReader(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+}
+
+template void FetchBody<Request>::LockStream(JSContext* aCx,
+ ReadableStream* aStream,
+ ErrorResult& aRv);
+
+template void FetchBody<Response>::LockStream(JSContext* aCx,
+ ReadableStream* aStream,
+ ErrorResult& aRv);
+
+template <class Derived>
+void FetchBody<Derived>::MaybeTeeReadableStreamBody(
+ JSContext* aCx, ReadableStream** aBodyOut,
+ FetchStreamReader** aStreamReader, nsIInputStream** aInputStream,
+ ErrorResult& aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(aStreamReader);
+ MOZ_DIAGNOSTIC_ASSERT(aInputStream);
+ MOZ_DIAGNOSTIC_ASSERT(!BodyUsed());
+
+ *aBodyOut = nullptr;
+ *aStreamReader = nullptr;
+ *aInputStream = nullptr;
+
+ if (!mReadableStreamBody) {
+ return;
+ }
+
+ // If this is a ReadableStream with an native source, this has been
+ // generated by a Fetch. In this case, Fetch will be able to recreate it
+ // again when GetBody() is called.
+ if (mReadableStreamBody->MaybeGetInputStreamIfUnread()) {
+ *aBodyOut = nullptr;
+ return;
+ }
+
+ nsTArray<RefPtr<ReadableStream> > branches;
+ MOZ_KnownLive(mReadableStreamBody)->Tee(aCx, branches, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ mReadableStreamBody = branches[0];
+ branches[1].forget(aBodyOut);
+
+ aRv = FetchStreamReader::Create(aCx, mOwner, aStreamReader, aInputStream);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+}
+
+template void FetchBody<Request>::MaybeTeeReadableStreamBody(
+ JSContext* aCx, ReadableStream** aBodyOut,
+ FetchStreamReader** aStreamReader, nsIInputStream** aInputStream,
+ ErrorResult& aRv);
+
+template void FetchBody<Response>::MaybeTeeReadableStreamBody(
+ JSContext* aCx, ReadableStream** aBodyOut,
+ 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();
+
+ RefPtr<ReadableStream> body(mReadableStreamBody);
+ IgnoredErrorResult result;
+
+ JS::Rooted<JS::Value> abortReason(cx);
+
+ AbortSignalImpl* signalImpl = Signal();
+ if (signalImpl) {
+ abortReason.set(signalImpl->RawReason());
+ }
+
+ AbortStream(cx, body, result, abortReason);
+}
+
+template void FetchBody<Request>::RunAbortAlgorithm();
+
+template void FetchBody<Response>::RunAbortAlgorithm();
+
+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>)
+ 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>)
+ 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,
+ const nsACString& aMixedCaseMimeType,
+ already_AddRefed<nsIInputStream> aBodyStream)
+ : FetchBody<EmptyBody>(aGlobal),
+ mAbortSignalImpl(aAbortSignalImpl),
+ mMimeType(aMimeType),
+ mMixedCaseMimeType(aMixedCaseMimeType),
+ 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,
+ const nsACString& aMixedCaseMimeType, 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,
+ aMixedCaseMimeType, 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/Fetch.h b/dom/fetch/Fetch.h
new file mode 100644
index 0000000000..9b9dccd1ee
--- /dev/null
+++ b/dom/fetch/Fetch.h
@@ -0,0 +1,310 @@
+/* -*- 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 "mozilla/Attributes.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/Promise.h"
+#include "mozilla/dom/FetchStreamReader.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamDefaultReaderBinding.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/workerinternals/RuntimeService.h"
+
+class nsIGlobalObject;
+class nsIEventTarget;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
+class
+ BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString;
+class BlobImpl;
+class InternalRequest;
+class
+ OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
+
+class ReadableStreamDefaultReader;
+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 {
+using BodyInit =
+ BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
+using ResponseBodyInit =
+ BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString;
+using OwningBodyInit =
+ OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
+}; // 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.
+ */
+
+class FetchBodyBase : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(FetchBodyBase)
+
+ protected:
+ virtual ~FetchBodyBase() = default;
+
+ RefPtr<ReadableStream> mReadableStreamBody;
+};
+
+template <class Derived>
+class FetchBody : public FetchBodyBase, public AbortFollower {
+ public:
+ using FetchBodyBase::QueryInterface;
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(FetchBody, FetchBodyBase)
+
+ bool BodyUsed() 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);
+ }
+
+ already_AddRefed<ReadableStream> GetBody(JSContext* aCx, ErrorResult& aRv);
+ void GetMimeType(nsACString& aMimeType, nsACString& aMixedCaseMimeType);
+
+ const nsACString& BodyBlobURISpec() const;
+
+ const nsAString& BodyLocalPath() const;
+
+ // If the body contains a ReadableStream body object, this method produces a
+ // tee() of it.
+ //
+ // This is marked as a script boundary minimize changes required for
+ // annotation while we work out how to correctly annotate this code.
+ // Tracked in Bug 1750650.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void MaybeTeeReadableStreamBody(JSContext* aCx, ReadableStream** 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);
+
+ virtual AbortSignalImpl* GetSignalImpl() const = 0;
+
+ virtual AbortSignalImpl* GetSignalImplToConsumeBody() const = 0;
+
+ // AbortFollower
+ void RunAbortAlgorithm() override;
+
+ already_AddRefed<Promise> ConsumeBody(JSContext* aCx,
+ BodyConsumer::ConsumeType aType,
+ ErrorResult& aRv);
+
+ protected:
+ nsCOMPtr<nsIGlobalObject> mOwner;
+
+ // This is the Reader used to retrieve data from the body. This needs to be
+ // traversed by subclasses.
+ RefPtr<FetchStreamReader> mFetchStreamReader;
+
+ explicit FetchBody(nsIGlobalObject* aOwner);
+
+ virtual ~FetchBody();
+
+ void SetReadableStreamBody(JSContext* aCx, ReadableStream* aBody);
+
+ private:
+ Derived* DerivedClass() const {
+ return static_cast<Derived*>(const_cast<FetchBody*>(this));
+ }
+
+ void LockStream(JSContext* aCx, ReadableStream* aStream, ErrorResult& aRv);
+
+ void AssertIsOnTargetThread() {
+ MOZ_ASSERT(NS_IsMainThread() == !GetCurrentThreadWorkerPrivate());
+ }
+
+ // Only ever set once, always on target thread.
+ bool mBodyUsed;
+
+ // The main-thread event target for runnable dispatching.
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+};
+
+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,
+ const nsACString& aMixedCaseMimeType, ErrorResult& aRv);
+
+ nsIGlobalObject* GetParentObject() const { return mOwner; }
+
+ AbortSignalImpl* GetSignalImpl() const override { return mAbortSignalImpl; }
+ AbortSignalImpl* GetSignalImplToConsumeBody() const final { return nullptr; }
+
+ const UniquePtr<mozilla::ipc::PrincipalInfo>& GetPrincipalInfo() const {
+ return mPrincipalInfo;
+ }
+
+ void GetMimeType(nsACString& aMimeType, nsACString& aMixedCaseMimeType) {
+ aMimeType = mMimeType;
+ aMixedCaseMimeType = mMixedCaseMimeType;
+ }
+
+ 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,
+ const nsACString& aMixedCaseMimeType,
+ already_AddRefed<nsIInputStream> aBodyStream);
+
+ ~EmptyBody();
+
+ UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
+ RefPtr<AbortSignalImpl> mAbortSignalImpl;
+ nsCString mMimeType;
+ nsCString mMixedCaseMimeType;
+ nsCOMPtr<nsIInputStream> mBodyStream;
+};
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Fetch_h
diff --git a/dom/fetch/FetchChild.cpp b/dom/fetch/FetchChild.cpp
new file mode 100644
index 0000000000..774eb516e7
--- /dev/null
+++ b/dom/fetch/FetchChild.cpp
@@ -0,0 +1,380 @@
+/* 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 "FetchChild.h"
+#include "FetchLog.h"
+#include "FetchObserver.h"
+#include "InternalResponse.h"
+#include "Request.h"
+#include "Response.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/dom/PerformanceTiming.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/RemoteWorkerChild.h"
+#include "mozilla/dom/SecurityPolicyViolationEventBinding.h"
+#include "mozilla/dom/WorkerChannelInfo.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIGlobalObject.h"
+#include "nsIObserverService.h"
+#include "nsIRunnable.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS0(FetchChild)
+
+mozilla::ipc::IPCResult FetchChild::Recv__delete__(const nsresult&& aResult) {
+ FETCH_LOG(("FetchChild::Recv__delete__ [%p]", this));
+ if (mIsShutdown) {
+ return IPC_OK();
+ }
+ // Shutdown has not been called, so mWorkerRef->Private() should be still
+ // alive.
+ MOZ_ASSERT(mWorkerRef->Private());
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ if (mPromise->State() == Promise::PromiseState::Pending) {
+ if (NS_FAILED(aResult)) {
+ mPromise->MaybeReject(aResult);
+ if (mFetchObserver) {
+ mFetchObserver->SetState(FetchState::Errored);
+ }
+ } else {
+ mPromise->MaybeResolve(aResult);
+ if (mFetchObserver) {
+ mFetchObserver->SetState(FetchState::Complete);
+ }
+ }
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchChild::RecvOnResponseAvailableInternal(
+ ParentToChildInternalResponse&& aResponse) {
+ FETCH_LOG(("FetchChild::RecvOnResponseAvailableInternal [%p]", this));
+ if (mIsShutdown) {
+ return IPC_OK();
+ }
+ // Shutdown has not been called, so mWorkerRef->Private() should be still
+ // alive.
+ MOZ_ASSERT(mWorkerRef->Private());
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+ SafeRefPtr<InternalResponse> internalResponse =
+ InternalResponse::FromIPC(aResponse);
+ IgnoredErrorResult result;
+ internalResponse->Headers()->SetGuard(HeadersGuardEnum::Immutable, result);
+ MOZ_ASSERT(internalResponse);
+
+ if (internalResponse->Type() != ResponseType::Error) {
+ if (internalResponse->Type() == ResponseType::Opaque) {
+ internalResponse->GeneratePaddingInfo();
+ }
+
+ if (mFetchObserver) {
+ mFetchObserver->SetState(FetchState::Complete);
+ }
+ nsCOMPtr<nsIGlobalObject> global;
+ global = mWorkerRef->Private()->GlobalScope();
+ RefPtr<Response> response =
+ new Response(global, internalResponse.clonePtr(), mSignalImpl);
+ mPromise->MaybeResolve(response);
+
+ return IPC_OK();
+ }
+
+ FETCH_LOG(
+ ("FetchChild::RecvOnResponseAvailableInternal [%p] response type is "
+ "Error(0x%x)",
+ this, static_cast<int32_t>(internalResponse->GetErrorCode())));
+ if (mFetchObserver) {
+ mFetchObserver->SetState(FetchState::Errored);
+ }
+ mPromise->MaybeRejectWithTypeError<MSG_FETCH_FAILED>();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchChild::RecvOnResponseEnd(ResponseEndArgs&& aArgs) {
+ FETCH_LOG(("FetchChild::RecvOnResponseEnd [%p]", this));
+ if (mIsShutdown) {
+ return IPC_OK();
+ }
+ // Shutdown has not been called, so mWorkerRef->Private() should be still
+ // alive.
+ MOZ_ASSERT(mWorkerRef->Private());
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ if (aArgs.endReason() == FetchDriverObserver::eAborted) {
+ FETCH_LOG(
+ ("FetchChild::RecvOnResponseEnd [%p] endReason is eAborted", this));
+ if (mFetchObserver) {
+ mFetchObserver->SetState(FetchState::Errored);
+ }
+ mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ Unfollow();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchChild::RecvOnDataAvailable() {
+ FETCH_LOG(("FetchChild::RecvOnDataAvailable [%p]", this));
+ if (mIsShutdown) {
+ return IPC_OK();
+ }
+ // Shutdown has not been called, so mWorkerRef->Private() should be still
+ // alive.
+ MOZ_ASSERT(mWorkerRef->Private());
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ if (mFetchObserver && mFetchObserver->State() == FetchState::Requesting) {
+ mFetchObserver->SetState(FetchState::Responding);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchChild::RecvOnFlushConsoleReport(
+ nsTArray<net::ConsoleReportCollected>&& aReports) {
+ FETCH_LOG(("FetchChild::RecvOnFlushConsoleReport [%p]", this));
+ if (mIsShutdown) {
+ return IPC_OK();
+ }
+ // Shutdown has not been called, so mWorkerRef->Private() should be still
+ // alive.
+ MOZ_ASSERT(mWorkerRef->Private());
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mReporter);
+
+ RefPtr<ThreadSafeWorkerRef> workerRef = mWorkerRef;
+ nsCOMPtr<nsIConsoleReportCollector> reporter = mReporter;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [reports = std::move(aReports), reporter = std::move(reporter),
+ workerRef = std::move(workerRef)]() mutable {
+ for (const auto& report : reports) {
+ reporter->AddConsoleReport(
+ report.errorFlags(), report.category(),
+ static_cast<nsContentUtils::PropertiesFile>(
+ report.propertiesFile()),
+ report.sourceFileURI(), report.lineNumber(),
+ report.columnNumber(), report.messageName(),
+ report.stringParams());
+ }
+
+ if (workerRef->Private()->IsServiceWorker()) {
+ reporter->FlushReportsToConsoleForServiceWorkerScope(
+ workerRef->Private()->ServiceWorkerScope());
+ }
+
+ if (workerRef->Private()->IsSharedWorker()) {
+ workerRef->Private()
+ ->GetRemoteWorkerController()
+ ->FlushReportsOnMainThread(reporter);
+ }
+
+ reporter->FlushConsoleReports(workerRef->Private()->GetLoadGroup());
+ });
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+
+ return IPC_OK();
+}
+
+RefPtr<FetchChild> FetchChild::Create(WorkerPrivate* aWorkerPrivate,
+ RefPtr<Promise> aPromise,
+ RefPtr<AbortSignalImpl> aSignalImpl,
+ RefPtr<FetchObserver> aObserver) {
+ MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<FetchChild> actor = MakeRefPtr<FetchChild>(
+ std::move(aPromise), std::move(aSignalImpl), std::move(aObserver));
+
+ RefPtr<StrongWorkerRef> workerRef =
+ StrongWorkerRef::Create(aWorkerPrivate, "FetchChild", [actor]() {
+ FETCH_LOG(("StrongWorkerRef callback"));
+ actor->Shutdown();
+ });
+ if (NS_WARN_IF(!workerRef)) {
+ return nullptr;
+ }
+
+ actor->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+ if (NS_WARN_IF(!actor->mWorkerRef)) {
+ return nullptr;
+ }
+ return actor;
+}
+
+mozilla::ipc::IPCResult FetchChild::RecvOnCSPViolationEvent(
+ const nsAString& aJSON) {
+ FETCH_LOG(("FetchChild::RecvOnCSPViolationEvent [%p] aJSON: %s\n", this,
+ NS_ConvertUTF16toUTF8(aJSON).BeginReading()));
+
+ nsString JSON(aJSON);
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [JSON]() mutable {
+ SecurityPolicyViolationEventInit violationEventInit;
+ if (NS_WARN_IF(!violationEventInit.Init(JSON))) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv =
+ NS_NewURI(getter_AddRefs(uri), violationEventInit.mBlockedURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ return;
+ }
+
+ rv = observerService->NotifyObservers(
+ uri, CSP_VIOLATION_TOPIC, violationEventInit.mViolatedDirective.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ });
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+
+ if (mCSPEventListener) {
+ Unused << NS_WARN_IF(
+ NS_FAILED(mCSPEventListener->OnCSPViolationEvent(aJSON)));
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchChild::RecvOnReportPerformanceTiming(
+ ResponseTiming&& aTiming) {
+ FETCH_LOG(("FetchChild::RecvOnReportPerformanceTiming [%p]", this));
+ if (mIsShutdown) {
+ return IPC_OK();
+ }
+ // Shutdown has not been called, so mWorkerRef->Private() should be still
+ // alive.
+ MOZ_ASSERT(mWorkerRef->Private());
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ RefPtr<PerformanceStorage> performanceStorage =
+ mWorkerRef->Private()->GetPerformanceStorage();
+ if (performanceStorage) {
+ performanceStorage->AddEntry(
+ aTiming.entryName(), aTiming.initiatorType(),
+ MakeUnique<PerformanceTimingData>(aTiming.timingData()));
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FetchChild::RecvOnNotifyNetworkMonitorAlternateStack(
+ uint64_t aChannelID) {
+ FETCH_LOG(
+ ("FetchChild::RecvOnNotifyNetworkMonitorAlternateStack [%p]", this));
+ if (mIsShutdown) {
+ return IPC_OK();
+ }
+ // Shutdown has not been called, so mWorkerRef->Private() should be still
+ // alive.
+ MOZ_ASSERT(mWorkerRef->Private());
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ if (!mOriginStack) {
+ return IPC_OK();
+ }
+
+ if (!mWorkerChannelInfo) {
+ mWorkerChannelInfo = MakeRefPtr<WorkerChannelInfo>(
+ aChannelID, mWorkerRef->Private()->AssociatedBrowsingContextID());
+ }
+
+ // Unfortunately, SerializedStackHolder can only be read on the main thread.
+ // However, it doesn't block the fetch execution.
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [channel = mWorkerChannelInfo,
+ stack = std::move(mOriginStack)]() mutable {
+ NotifyNetworkMonitorAlternateStack(channel, std::move(stack));
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+
+ return IPC_OK();
+}
+
+void FetchChild::SetCSPEventListener(nsICSPEventListener* aListener) {
+ MOZ_ASSERT(aListener && !mCSPEventListener);
+ mCSPEventListener = aListener;
+}
+
+FetchChild::FetchChild(RefPtr<Promise>&& aPromise,
+ RefPtr<AbortSignalImpl>&& aSignalImpl,
+ RefPtr<FetchObserver>&& aObserver)
+ : mPromise(std::move(aPromise)),
+ mSignalImpl(std::move(aSignalImpl)),
+ mFetchObserver(std::move(aObserver)),
+ mReporter(new ConsoleReportCollector()) {
+ FETCH_LOG(("FetchChild::FetchChild [%p]", this));
+}
+
+void FetchChild::RunAbortAlgorithm() {
+ FETCH_LOG(("FetchChild::RunAbortAlgorithm [%p]", this));
+ if (mIsShutdown) {
+ return;
+ }
+ if (mWorkerRef) {
+ Unused << SendAbortFetchOp();
+ }
+}
+
+void FetchChild::DoFetchOp(const FetchOpArgs& aArgs) {
+ FETCH_LOG(("FetchChild::DoFetchOp [%p]", this));
+ if (mSignalImpl) {
+ if (mSignalImpl->Aborted()) {
+ Unused << SendAbortFetchOp();
+ return;
+ }
+ Follow(mSignalImpl);
+ }
+ Unused << SendFetchOp(aArgs);
+}
+
+void FetchChild::Shutdown() {
+ FETCH_LOG(("FetchChild::Shutdown [%p]", this));
+ if (mIsShutdown) {
+ return;
+ }
+ mIsShutdown.Flip();
+
+ // If mWorkerRef is nullptr here, that means Recv__delete__() must be called
+ if (!mWorkerRef) {
+ return;
+ }
+ mPromise = nullptr;
+ mFetchObserver = nullptr;
+ Unfollow();
+ mSignalImpl = nullptr;
+ mCSPEventListener = nullptr;
+ Unused << SendAbortFetchOp();
+ mWorkerRef = nullptr;
+}
+
+void FetchChild::ActorDestroy(ActorDestroyReason aReason) {
+ FETCH_LOG(("FetchChild::ActorDestroy [%p]", this));
+ mPromise = nullptr;
+ mFetchObserver = nullptr;
+ mSignalImpl = nullptr;
+ mCSPEventListener = nullptr;
+ mWorkerRef = nullptr;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/FetchChild.h b/dom/fetch/FetchChild.h
new file mode 100644
index 0000000000..986fc728cb
--- /dev/null
+++ b/dom/fetch/FetchChild.h
@@ -0,0 +1,91 @@
+/* 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_fetchChild_h__
+#define mozilla_dom_fetchChild_h__
+
+#include "mozilla/dom/AbortSignal.h"
+#include "mozilla/dom/AbortFollower.h"
+#include "mozilla/dom/FlippedOnce.h"
+#include "mozilla/dom/PFetchChild.h"
+#include "mozilla/dom/SerializedStackHolder.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsISupports.h"
+#include "nsIWorkerChannelInfo.h"
+
+namespace mozilla::dom {
+
+class FetchObserver;
+class ThreadSafeWorkerRef;
+class Promise;
+class WorkerPrivate;
+
+class FetchChild final : public PFetchChild, public AbortFollower {
+ friend class PFetchChild;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ mozilla::ipc::IPCResult Recv__delete__(const nsresult&& aResult);
+
+ mozilla::ipc::IPCResult RecvOnResponseAvailableInternal(
+ ParentToChildInternalResponse&& aResponse);
+
+ mozilla::ipc::IPCResult RecvOnResponseEnd(ResponseEndArgs&& aArgs);
+
+ mozilla::ipc::IPCResult RecvOnDataAvailable();
+
+ mozilla::ipc::IPCResult RecvOnFlushConsoleReport(
+ nsTArray<net::ConsoleReportCollected>&& aReports);
+
+ mozilla::ipc::IPCResult RecvOnCSPViolationEvent(const nsAString& aJSon);
+
+ mozilla::ipc::IPCResult RecvOnReportPerformanceTiming(
+ ResponseTiming&& aTiming);
+
+ mozilla::ipc::IPCResult RecvOnNotifyNetworkMonitorAlternateStack(
+ uint64_t aChannelID);
+
+ void SetCSPEventListener(nsICSPEventListener* aListener);
+
+ static RefPtr<FetchChild> Create(WorkerPrivate* aWorkerPrivate,
+ RefPtr<Promise> aPromise,
+ RefPtr<AbortSignalImpl> aSignalImpl,
+ RefPtr<FetchObserver> aObserver);
+
+ FetchChild(RefPtr<Promise>&& aPromise, RefPtr<AbortSignalImpl>&& aSignalImpl,
+ RefPtr<FetchObserver>&& aObserver);
+
+ // AbortFollower
+ void RunAbortAlgorithm() override;
+
+ void DoFetchOp(const FetchOpArgs& aArgs);
+
+ void SetOriginStack(UniquePtr<SerializedStackHolder>&& aStack) {
+ MOZ_ASSERT(!mOriginStack);
+ mOriginStack = std::move(aStack);
+ }
+
+ private:
+ ~FetchChild() = default;
+
+ // WorkerPrivate shutdown callback.
+ void Shutdown();
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+ RefPtr<Promise> mPromise;
+ RefPtr<AbortSignalImpl> mSignalImpl;
+ RefPtr<FetchObserver> mFetchObserver;
+ UniquePtr<SerializedStackHolder> mOriginStack;
+ nsCOMPtr<nsICSPEventListener> mCSPEventListener;
+ nsCOMPtr<nsIConsoleReportCollector> mReporter;
+ FlippedOnce<false> mIsShutdown;
+ nsCOMPtr<nsIWorkerChannelInfo> mWorkerChannelInfo;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp
new file mode 100644
index 0000000000..8c527ffeae
--- /dev/null
+++ b/dom/fetch/FetchDriver.cpp
@@ -0,0 +1,1755 @@
+/* -*- 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 "js/Value.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/dom/FetchDriver.h"
+
+#include "mozilla/dom/ReferrerInfo.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "mozilla/dom/Document.h"
+#include "nsICookieJarSettings.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIInterceptionInfo.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 "nsIRedirectHistoryEntry.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/PerformanceTiming.h"
+#include "mozilla/dom/ServiceWorkerInterceptController.h"
+#include "mozilla/dom/UserActivation.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/PreloaderBase.h"
+#include "mozilla/net/InterceptionInfo.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/StaticPrefs_javascript.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 = NS_GetFinalChannelURI(channel, 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->CancelWithReason(NS_BINDING_ABORTED,
+ "AlternativeDataStreamListener::Cancel"_ns);
+ 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);
+ 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 */);
+
+ MOZ_DIAGNOSTIC_ASSERT(!mCacheInfoChannel);
+ mCacheInfoChannel = cic;
+
+ // call FetchDriver::HttpFetch to load main body
+ MOZ_ASSERT(mFetchDriver);
+ return mFetchDriver->HttpFetch();
+ }
+ // 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);
+}
+
+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,
+ nsINetworkInterceptController)
+
+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);
+ if (mObserver) {
+ mObserver = nullptr;
+ }
+}
+
+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()) {
+ FetchDriverAbortActions(aSignalImpl);
+ 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(mDocument, 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 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);
+ }
+
+ if (mAssociatedBrowsingContextID) {
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ rv = loadInfo->SetWorkerAssociatedBrowsingContextID(
+ mAssociatedBrowsingContextID);
+ }
+
+ // If the fetch is created by FetchEvent.request or NavigationPreload request,
+ // corresponding InterceptedHttpChannel information need to propagte to the
+ // channel of the fetch.
+ if (mRequest->GetInterceptionTriggeringPrincipalInfo()) {
+ auto principalOrErr = mozilla::ipc::PrincipalInfoToPrincipal(
+ *(mRequest->GetInterceptionTriggeringPrincipalInfo().get()));
+ if (!principalOrErr.isErr()) {
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+
+ nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>> redirectChain;
+ if (!mRequest->InterceptionRedirectChain().IsEmpty()) {
+ for (const RedirectHistoryEntryInfo& entryInfo :
+ mRequest->InterceptionRedirectChain()) {
+ nsCOMPtr<nsIRedirectHistoryEntry> entry =
+ mozilla::ipc::RHEntryInfoToRHEntry(entryInfo);
+ redirectChain.AppendElement(entry);
+ }
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ MOZ_ASSERT(loadInfo);
+ loadInfo->SetInterceptionInfo(new mozilla::net::InterceptionInfo(
+ principal, mRequest->InterceptionContentPolicyType(), redirectChain,
+ mRequest->InterceptionFromThirdParty()));
+ }
+ }
+
+ if (mDocument && mDocument->GetEmbedderElement() &&
+ mDocument->GetEmbedderElement()->IsAnyOfHTMLElements(nsGkAtoms::object,
+ nsGkAtoms::embed)) {
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ rv = loadInfo->SetIsFromObjectOrEmbed(true);
+ 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, 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);
+
+ rv = internalChan->SetRequestMode(mRequest->Mode());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // Conversion between enumerations is safe due to static asserts in
+ // dom/workers/ServiceWorkerManager.cpp
+ 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 (mObserver && httpChan) {
+ mObserver->OnNotifyNetworkMonitorAlternateStack(httpChan->ChannelId());
+ }
+
+ // 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,
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC);
+ 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()) {
+ MOZ_ASSERT(!FetchUtil::WasmAltDataType.IsEmpty());
+ nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(chan);
+ if (cic && StaticPrefs::javascript_options_wasm_caching() &&
+ !mRequest->SkipWasmCaching()) {
+ cic->PreferAlternativeDataType(
+ FetchUtil::WasmAltDataType, nsLiteralCString(WASM_CONTENT_TYPE),
+ nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::
+ SERIALIZE);
+ }
+ }
+
+ 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;
+}
+
+SafeRefPtr<InternalResponse> FetchDriver::BeginAndGetFilteredResponse(
+ SafeRefPtr<InternalResponse> aResponse, bool aFoundOpaqueRedirect) {
+ MOZ_ASSERT(aResponse);
+ AutoTArray<nsCString, 4> reqURLList;
+ mRequest->GetURLListWithoutFragment(reqURLList);
+ MOZ_ASSERT(!reqURLList.IsEmpty());
+ aResponse->SetURLList(reqURLList);
+ SafeRefPtr<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);
+ MOZ_ASSERT(filteredResponse);
+ if (!ShouldCheckSRI(*mRequest, *filteredResponse)) {
+ // Need to keep mObserver alive.
+ RefPtr<FetchDriverObserver> observer = mObserver;
+ observer->OnResponseAvailable(filteredResponse.clonePtr());
+#ifdef DEBUG
+ mResponseAvailableCalled = true;
+#endif
+ }
+
+ return filteredResponse;
+}
+
+void FetchDriver::FailWithNetworkError(nsresult rv) {
+ AssertIsOnMainThread();
+ if (mObserver) {
+ // Need to keep mObserver alive.
+ RefPtr<FetchDriverObserver> observer = mObserver;
+ observer->OnResponseAvailable(InternalResponse::NetworkError(rv));
+#ifdef DEBUG
+ mResponseAvailableCalled = true;
+#endif
+ }
+
+ // mObserver could be null after OnResponseAvailable().
+ if (mObserver) {
+ mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking,
+ JS::UndefinedHandleValue);
+ mObserver = nullptr;
+ }
+
+ mChannel = nullptr;
+ Unfollow();
+}
+
+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->CancelWithReason(NS_BINDING_ABORTED,
+ "FetchDriver::OnStartRequest aborted"_ns);
+ return NS_BINDING_ABORTED;
+ }
+
+ if (!mChannel) {
+ // if the request is aborted, we remove the mObserver reference in
+ // OnStopRequest or ~FetchDriver()
+ MOZ_ASSERT_IF(!mAborted, !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();
+
+ SafeRefPtr<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 = 0;
+ rv = httpChannel->GetResponseStatus(&responseStatus);
+ if (NS_FAILED(rv)) {
+ FailWithNetworkError(rv);
+ return 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 = MakeSafeRefPtr<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 = MakeSafeRefPtr<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().Equals(
+ FetchUtil::WasmAltDataType));
+ 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;
+ 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 */);
+ 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<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(std::move(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)) {
+ RefPtr<TaskQueue> queue =
+ TaskQueue::Create(sts.forget(), "FetchDriver STS Delivery Queue");
+ Unused << NS_WARN_IF(NS_FAILED(rr->RetargetDeliveryTo(queue)));
+ }
+ 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 for accesses in OnStartRequest, OnStopRequest and
+ // member functions accessed before opening the channel.
+ // However, we have a possibility of a race from FetchDriverAbortActions.
+ // Hence, we need to ensure that we are not modifying any members accessed by
+ // FetchDriver::FetchDriverAbortActions
+
+ 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;
+ }
+ }
+ }
+ }
+
+ if (!mResponse) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Needs to be initialized to 0 because in some cases nsStringInputStream may
+ // not write to aRead.
+ uint32_t aRead = 0;
+ 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;
+
+ if (mObserver && mAborted) {
+ // fetch request was aborted.
+ // We have already sent the observer
+ // notification that request has been aborted in FetchDriverAbortActions.
+ // Remove the observer reference and don't push anymore notifications.
+ mObserver = nullptr;
+ }
+
+ // main data loading is going to finish, breaking the reference cycle.
+ RefPtr<AlternativeDataStreamListener> altDataListener =
+ std::move(mAltDataListener);
+
+ // For PFetch and ServiceWorker navigationPreload, resource timing should be
+ // reported before the body stream closing.
+ if (mObserver) {
+ mObserver->OnReportPerformanceTiming();
+ }
+
+ // 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.clonePtr());
+#ifdef DEBUG
+ mResponseAvailableCalled = true;
+#endif
+ }
+ }
+
+ if (mObserver) {
+ mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking,
+ JS::UndefinedHandleValue);
+ mObserver = nullptr;
+ }
+
+ mChannel = nullptr;
+ Unfollow();
+}
+
+NS_IMETHODIMP
+FetchDriver::ShouldPrepareForIntercept(nsIURI* aURI, nsIChannel* aChannel,
+ bool* aShouldIntercept) {
+ MOZ_ASSERT(aChannel);
+
+ if (mInterceptController) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return mInterceptController->ShouldPrepareForIntercept(aURI, aChannel,
+ aShouldIntercept);
+ }
+
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ NS_QueryNotificationCallbacks(nullptr, mLoadGroup,
+ NS_GET_IID(nsINetworkInterceptController),
+ getter_AddRefs(controller));
+ if (controller) {
+ return controller->ShouldPrepareForIntercept(aURI, aChannel,
+ aShouldIntercept);
+ }
+
+ *aShouldIntercept = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FetchDriver::ChannelIntercepted(nsIInterceptedChannel* aChannel) {
+ if (mInterceptController) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return mInterceptController->ChannelIntercepted(aChannel);
+ }
+
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ NS_QueryNotificationCallbacks(nullptr, mLoadGroup,
+ NS_GET_IID(nsINetworkInterceptController),
+ getter_AddRefs(controller));
+ if (controller) {
+ return controller->ChannelIntercepted(aChannel);
+ }
+
+ return NS_OK;
+}
+
+void FetchDriver::EnableNetworkInterceptControl() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mInterceptController);
+ mInterceptController = new ServiceWorkerInterceptController();
+}
+
+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);
+
+ // we need to strip Authentication headers for cross-origin requests
+ // Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
+ bool skipAuthHeader =
+ (StaticPrefs::network_fetch_redirect_stripAuthHeader() &&
+ NS_ShouldRemoveAuthHeaderOnRedirect(aOldChannel, aNewChannel, aFlags));
+
+ SetRequestHeaders(newHttpChannel, rewriteToGET, skipAuthHeader);
+ }
+
+ // "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(NS_GetFinalChannelURI(aNewChannel, 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;
+}
+
+PerformanceTimingData* FetchDriver::GetPerformanceTimingData(
+ nsAString& aInitiatorType, nsAString& aEntryName) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!mChannel) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel);
+ if (!timedChannel) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+ if (!httpChannel) {
+ return nullptr;
+ }
+ return dom::PerformanceTimingData::Create(timedChannel, httpChannel, 0,
+ aInitiatorType, aEntryName);
+}
+
+void FetchDriver::SetRequestHeaders(nsIHttpChannel* aChannel,
+ bool aStripRequestBodyHeader,
+ bool aStripAuthHeader) 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;
+ }
+
+ if (aStripAuthHeader &&
+ headers[i].mName.LowerCaseEqualsASCII("authorization")) {
+ 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));
+ }
+ }
+}
+
+void FetchDriver::RunAbortAlgorithm() { FetchDriverAbortActions(Signal()); }
+
+void FetchDriver::FetchDriverAbortActions(AbortSignalImpl* aSignalImpl) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ if (mObserver) {
+#ifdef DEBUG
+ mResponseAvailableCalled = true;
+#endif
+ JS::Rooted<JS::Value> reason(RootingCx());
+ if (aSignalImpl) {
+ reason.set(aSignalImpl->RawReason());
+ }
+ mObserver->OnResponseEnd(FetchDriverObserver::eAborted, reason);
+ // As a part of cleanup, we are not removing the mObserver reference as it
+ // could race with mObserver access in OnDataAvailable when it runs OMT.
+ // We will be removing the reference in the OnStopRequest which guaranteed
+ // to run after cancelling the channel.
+ }
+
+ if (mChannel) {
+ mChannel->CancelWithReason(NS_BINDING_ABORTED,
+ "FetchDriver::RunAbortAlgorithm"_ns);
+ 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..84391ef951
--- /dev/null
+++ b/dom/fetch/FetchDriver.h
@@ -0,0 +1,232 @@
+/* -*- 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 "nsINetworkInterceptController.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;
+class PerformanceTimingData;
+
+/**
+ * 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(SafeRefPtr<InternalResponse> aResponse);
+
+ enum EndReason {
+ eAborted,
+ eByNetworking,
+ };
+
+ virtual void OnResponseEnd(EndReason aReason,
+ JS::Handle<JS::Value> aReasonDetails){};
+
+ 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;
+
+ virtual void OnReportPerformanceTiming() {}
+
+ virtual void OnNotifyNetworkMonitorAlternateStack(uint64_t aChannelID) {}
+
+ protected:
+ virtual ~FetchDriverObserver() = default;
+
+ virtual void OnResponseAvailableInternal(
+ SafeRefPtr<InternalResponse> aResponse) = 0;
+
+ nsCOMPtr<nsIConsoleReportCollector> mReporter;
+
+ private:
+ bool mGotResponseAvailable;
+};
+
+class AlternativeDataStreamListener;
+
+class FetchDriver final : public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIInterfaceRequestor,
+ public nsINetworkInterceptController,
+ public nsIThreadRetargetableStreamListener,
+ public AbortFollower {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSINETWORKINTERCEPTCONTROLLER
+ 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);
+ }
+
+ PerformanceTimingData* GetPerformanceTimingData(nsAString& aInitiatorType,
+ nsAString& aEntryName);
+
+ // AbortFollower
+ void RunAbortAlgorithm() override;
+ void FetchDriverAbortActions(AbortSignalImpl* aSignalImpl);
+
+ void EnableNetworkInterceptControl();
+
+ void SetAssociatedBrowsingContextID(uint64_t aID) {
+ mAssociatedBrowsingContextID = aID;
+ }
+
+ private:
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ SafeRefPtr<InternalRequest> mRequest;
+ SafeRefPtr<InternalResponse> mResponse;
+ nsCOMPtr<nsIOutputStream> mPipeOutputStream;
+ // Access to mObserver can be racy from OnDataAvailable and
+ // FetchAbortActions. This must not be modified
+ // in either of these functions.
+ 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
+ nsCOMPtr<nsINetworkInterceptController> mInterceptController;
+
+ uint64_t mAssociatedBrowsingContextID{0};
+
+ 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.
+ SafeRefPtr<InternalResponse> BeginAndGetFilteredResponse(
+ SafeRefPtr<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,
+ bool aStripAuthHeader) 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..343a5acb3f
--- /dev/null
+++ b/dom/fetch/FetchIPCTypes.h
@@ -0,0 +1,63 @@
+/* -*- 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"
+#include "mozilla/dom/FetchDriver.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_> {};
+template <>
+struct ParamTraits<mozilla::dom::FetchDriverObserver::EndReason>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::dom::FetchDriverObserver::EndReason,
+ mozilla::dom::FetchDriverObserver::eAborted,
+ mozilla::dom::FetchDriverObserver::eByNetworking> {};
+} // namespace IPC
+
+#endif // mozilla_dom_fetch_IPCUtils_h
diff --git a/dom/fetch/FetchLog.h b/dom/fetch/FetchLog.h
new file mode 100644
index 0000000000..37ef66e989
--- /dev/null
+++ b/dom/fetch/FetchLog.h
@@ -0,0 +1,19 @@
+/* -*- 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_FetchLog_h
+#define _mozilla_dom_FetchLog_h
+
+#include "mozilla/Logging.h"
+
+namespace mozilla::dom {
+
+extern mozilla::LazyLogModule gFetchLog;
+
+#define FETCH_LOG(args) MOZ_LOG(gFetchLog, LogLevel::Debug, args)
+
+} // namespace mozilla::dom
+#endif
diff --git a/dom/fetch/FetchObserver.cpp b/dom/fetch/FetchObserver.cpp
new file mode 100644
index 0000000000..0c9d1b9f7a
--- /dev/null
+++ b/dom/fetch/FetchObserver.cpp
@@ -0,0 +1,79 @@
+/* -*- 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)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchObserver,
+ DOMEventTargetHelper)
+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..a80342a2fd
--- /dev/null
+++ b/dom/fetch/FetchObserver.h
@@ -0,0 +1,45 @@
+/* -*- 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::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 mozilla::dom
+
+#endif // mozilla_dom_FetchObserver_h
diff --git a/dom/fetch/FetchParent.cpp b/dom/fetch/FetchParent.cpp
new file mode 100644
index 0000000000..93e9b9e43e
--- /dev/null
+++ b/dom/fetch/FetchParent.cpp
@@ -0,0 +1,340 @@
+/* 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 "FetchLog.h"
+#include "FetchParent.h"
+#include "FetchService.h"
+#include "InternalRequest.h"
+#include "InternalResponse.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/FetchTypes.h"
+#include "mozilla/dom/PerformanceTimingTypes.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS(FetchParent::FetchParentCSPEventListener, nsICSPEventListener)
+
+FetchParent::FetchParentCSPEventListener::FetchParentCSPEventListener(
+ const nsID& aActorID, nsCOMPtr<nsISerialEventTarget> aEventTarget)
+ : mActorID(aActorID), mEventTarget(aEventTarget) {
+ MOZ_ASSERT(mEventTarget);
+ FETCH_LOG(("FetchParentCSPEventListener [%p] actor ID: %s", this,
+ mActorID.ToString().get()));
+}
+
+NS_IMETHODIMP FetchParent::FetchParentCSPEventListener::OnCSPViolationEvent(
+ const nsAString& aJSON) {
+ AssertIsOnMainThread();
+ FETCH_LOG(("FetchParentCSPEventListener::OnCSPViolationEvent [%p]", this));
+
+ nsAutoString json(aJSON);
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction(__func__, [actorID = mActorID, json]() mutable {
+ FETCH_LOG(
+ ("FetchParentCSPEventListener::OnCSPViolationEvent, Runnale"));
+ RefPtr<FetchParent> actor = FetchParent::GetActorByID(actorID);
+ if (actor) {
+ actor->OnCSPViolationEvent(json);
+ }
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(mEventTarget->Dispatch(r, nsIThread::DISPATCH_NORMAL));
+ return NS_OK;
+}
+
+nsTHashMap<nsIDHashKey, RefPtr<FetchParent>> FetchParent::sActorTable;
+
+/*static*/
+RefPtr<FetchParent> FetchParent::GetActorByID(const nsID& aID) {
+ AssertIsOnBackgroundThread();
+ auto entry = sActorTable.Lookup(aID);
+ if (entry) {
+ return entry.Data();
+ }
+ return nullptr;
+}
+
+FetchParent::FetchParent() : mID(nsID::GenerateUUID()) {
+ FETCH_LOG(("FetchParent::FetchParent [%p]", this));
+ AssertIsOnBackgroundThread();
+ mBackgroundEventTarget = GetCurrentSerialEventTarget();
+ MOZ_ASSERT(mBackgroundEventTarget);
+ if (!sActorTable.WithEntryHandle(mID, [&](auto&& entry) {
+ if (entry.HasEntry()) {
+ return false;
+ }
+ entry.Insert(this);
+ return true;
+ })) {
+ FETCH_LOG(("FetchParent::FetchParent entry[%p] already exists", this));
+ }
+}
+
+FetchParent::~FetchParent() {
+ FETCH_LOG(("FetchParent::~FetchParent [%p]", this));
+ // MOZ_ASSERT(!mBackgroundEventTarget);
+ MOZ_ASSERT(!mResponsePromises);
+ MOZ_ASSERT(mActorDestroyed && mIsDone);
+}
+
+IPCResult FetchParent::RecvFetchOp(FetchOpArgs&& aArgs) {
+ FETCH_LOG(("FetchParent::RecvFetchOp [%p]", this));
+ AssertIsOnBackgroundThread();
+
+ MOZ_ASSERT(!mIsDone);
+ if (mActorDestroyed) {
+ return IPC_OK();
+ }
+
+ mRequest = MakeSafeRefPtr<InternalRequest>(std::move(aArgs.request()));
+ mPrincipalInfo = std::move(aArgs.principalInfo());
+ mWorkerScript = aArgs.workerScript();
+ mClientInfo = Some(ClientInfo(aArgs.clientInfo()));
+ if (aArgs.controller().isSome()) {
+ mController = Some(ServiceWorkerDescriptor(aArgs.controller().ref()));
+ }
+ mCookieJarSettings = aArgs.cookieJarSettings();
+ mNeedOnDataAvailable = aArgs.needOnDataAvailable();
+ mHasCSPEventListener = aArgs.hasCSPEventListener();
+
+ if (mHasCSPEventListener) {
+ mCSPEventListener =
+ MakeRefPtr<FetchParentCSPEventListener>(mID, mBackgroundEventTarget);
+ }
+ mAssociatedBrowsingContextID = aArgs.associatedBrowsingContextID();
+
+ MOZ_ASSERT(!mPromise);
+ mPromise = new GenericPromise::Private(__func__);
+
+ RefPtr<FetchParent> self = this;
+ mPromise->Then(
+ mBackgroundEventTarget, __func__,
+ [self](const bool&& result) mutable {
+ FETCH_LOG(
+ ("FetchParent::RecvFetchOp [%p] Success Callback", self.get()));
+ AssertIsOnBackgroundThread();
+ self->mPromise = nullptr;
+ if (self->mIsDone) {
+ FETCH_LOG(("FetchParent::RecvFetchOp [%p] Fetch has already aborted",
+ self.get()));
+ if (!self->mActorDestroyed) {
+ Unused << NS_WARN_IF(
+ !self->Send__delete__(self, NS_ERROR_DOM_ABORT_ERR));
+ }
+ return;
+ }
+ self->mIsDone = true;
+ if (!self->mActorDestroyed && !self->mExtendForCSPEventListener) {
+ FETCH_LOG(("FetchParent::RecvFetchOp [%p] Send__delete__(NS_OK)",
+ self.get()));
+ Unused << NS_WARN_IF(!self->Send__delete__(self, NS_OK));
+ }
+ },
+ [self](const nsresult&& aErr) mutable {
+ FETCH_LOG(
+ ("FetchParent::RecvFetchOp [%p] Failure Callback", self.get()));
+ AssertIsOnBackgroundThread();
+ self->mIsDone = true;
+ self->mPromise = nullptr;
+ if (!self->mActorDestroyed) {
+ FETCH_LOG(("FetchParent::RecvFetchOp [%p] Send__delete__(aErr)",
+ self.get()));
+ Unused << NS_WARN_IF(!self->Send__delete__(self, aErr));
+ }
+ });
+
+ RefPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [self]() mutable {
+ FETCH_LOG(
+ ("FetchParent::RecvFetchOp [%p], Main Thread Runnable", self.get()));
+ AssertIsOnMainThread();
+ if (self->mIsDone) {
+ MOZ_ASSERT(!self->mResponsePromises);
+ MOZ_ASSERT(self->mPromise);
+ FETCH_LOG(
+ ("FetchParent::RecvFetchOp [%p], Main Thread Runnable, "
+ "already aborted",
+ self.get()));
+ self->mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
+ return;
+ }
+ RefPtr<FetchService> fetchService = FetchService::GetInstance();
+ MOZ_ASSERT(fetchService);
+ MOZ_ASSERT(!self->mResponsePromises);
+ self->mResponsePromises =
+ fetchService->Fetch(AsVariant(FetchService::WorkerFetchArgs(
+ {self->mRequest.clonePtr(), self->mPrincipalInfo,
+ self->mWorkerScript, self->mClientInfo, self->mController,
+ self->mCookieJarSettings, self->mNeedOnDataAvailable,
+ self->mCSPEventListener, self->mAssociatedBrowsingContextID,
+ self->mBackgroundEventTarget, self->mID})));
+
+ self->mResponsePromises->GetResponseEndPromise()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self](ResponseEndArgs&& aArgs) mutable {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(self->mPromise);
+ self->mPromise->Resolve(true, __func__);
+ self->mResponsePromises = nullptr;
+ },
+ [self](CopyableErrorResult&& aErr) mutable {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(self->mPromise);
+ self->mPromise->Reject(aErr.StealNSResult(), __func__);
+ self->mResponsePromises = nullptr;
+ });
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_DispatchToMainThread(r.forget(), nsIThread::DISPATCH_NORMAL));
+
+ return IPC_OK();
+}
+
+IPCResult FetchParent::RecvAbortFetchOp() {
+ FETCH_LOG(("FetchParent::RecvAbortFetchOp [%p]", this));
+ AssertIsOnBackgroundThread();
+
+ if (mIsDone) {
+ FETCH_LOG(("FetchParent::RecvAbortFetchOp [%p], Already aborted", this));
+ return IPC_OK();
+ }
+ mIsDone = true;
+
+ RefPtr<FetchParent> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [self]() mutable {
+ FETCH_LOG(("FetchParent::RecvAbortFetchOp Runnable"));
+ AssertIsOnMainThread();
+ if (self->mResponsePromises) {
+ RefPtr<FetchService> fetchService = FetchService::GetInstance();
+ MOZ_ASSERT(fetchService);
+ fetchService->CancelFetch(std::move(self->mResponsePromises));
+ }
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_DispatchToMainThread(r.forget(), nsIThread::DISPATCH_NORMAL));
+
+ return IPC_OK();
+}
+
+void FetchParent::OnResponseAvailableInternal(
+ SafeRefPtr<InternalResponse>&& aResponse) {
+ FETCH_LOG(("FetchParent::OnResponseAvailableInternal [%p]", this));
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aResponse);
+ MOZ_ASSERT(!mActorDestroyed);
+
+ if (mIsDone && aResponse->Type() != ResponseType::Error) {
+ FETCH_LOG(
+ ("FetchParent::OnResponseAvailableInternal [%p] "
+ "Fetch has already aborted",
+ this));
+ return;
+ }
+
+ // To monitor the stream status between processes, response's body can not be
+ // serialized as RemoteLazyInputStream. Such that stream close can be
+ // propagated to FetchDriver in the parent process.
+ aResponse->SetSerializeAsLazy(false);
+
+ // CSP violation notification is asynchronous. Extending the FetchParent's
+ // life cycle for the notificaiton.
+ if (aResponse->Type() == ResponseType::Error &&
+ aResponse->GetErrorCode() == NS_ERROR_CONTENT_BLOCKED &&
+ mCSPEventListener) {
+ FETCH_LOG(
+ ("FetchParent::OnResponseAvailableInternal [%p] "
+ "NS_ERROR_CONTENT_BLOCKED",
+ this));
+ mExtendForCSPEventListener = true;
+ }
+
+ Unused << SendOnResponseAvailableInternal(
+ aResponse->ToParentToChildInternalResponse(WrapNotNull(Manager())));
+}
+
+void FetchParent::OnResponseEnd(const ResponseEndArgs& aArgs) {
+ FETCH_LOG(("FetchParent::OnResponseEnd [%p]", this));
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ if (mIsDone && aArgs.endReason() != FetchDriverObserver::eAborted) {
+ FETCH_LOG(
+ ("FetchParent::OnResponseEnd [%p] "
+ "Fetch has already aborted",
+ this));
+ return;
+ }
+
+ Unused << SendOnResponseEnd(aArgs);
+}
+
+void FetchParent::OnDataAvailable() {
+ FETCH_LOG(("FetchParent::OnDataAvailable [%p]", this));
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ Unused << SendOnDataAvailable();
+}
+
+void FetchParent::OnFlushConsoleReport(
+ const nsTArray<net::ConsoleReportCollected>& aReports) {
+ FETCH_LOG(("FetchParent::OnFlushConsoleReport [%p]", this));
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ Unused << SendOnFlushConsoleReport(aReports);
+}
+
+void FetchParent::OnReportPerformanceTiming(const ResponseTiming&& aTiming) {
+ FETCH_LOG(("FetchParent::OnReportPerformanceTiming [%p]", this));
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ Unused << SendOnReportPerformanceTiming(aTiming);
+}
+
+void FetchParent::OnNotifyNetworkMonitorAlternateStack(uint64_t aChannelID) {
+ FETCH_LOG(("FetchParent::OnNotifyNetworkMonitorAlternateStack [%p]", this));
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ Unused << SendOnNotifyNetworkMonitorAlternateStack(aChannelID);
+}
+
+void FetchParent::ActorDestroy(ActorDestroyReason aReason) {
+ FETCH_LOG(("FetchParent::ActorDestroy [%p]", this));
+ AssertIsOnBackgroundThread();
+ mActorDestroyed = true;
+ auto entry = sActorTable.Lookup(mID);
+ if (entry) {
+ entry.Remove();
+ FETCH_LOG(("FetchParent::ActorDestroy entry [%p] removed", this));
+ }
+ // Force to abort the existing fetch.
+ // Actor can be destoried by shutdown when still fetching.
+ RecvAbortFetchOp();
+ // mBackgroundEventTarget = nullptr;
+}
+
+nsICSPEventListener* FetchParent::GetCSPEventListener() {
+ return mCSPEventListener;
+}
+
+void FetchParent::OnCSPViolationEvent(const nsAString& aJSON) {
+ FETCH_LOG(("FetchParent::OnCSPViolationEvent [%p]", this));
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mHasCSPEventListener);
+ MOZ_ASSERT(!mActorDestroyed);
+
+ Unused << SendOnCSPViolationEvent(aJSON);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/FetchParent.h b/dom/fetch/FetchParent.h
new file mode 100644
index 0000000000..e373d93b73
--- /dev/null
+++ b/dom/fetch/FetchParent.h
@@ -0,0 +1,109 @@
+/* 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_fetchParent_h__
+#define mozilla_dom_fetchParent_h__
+
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/PFetchParent.h"
+#include "mozilla/dom/SafeRefPtr.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsCOMPtr.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsID.h"
+#include "nsISerialEventTarget.h"
+#include "nsString.h"
+#include "nsTHashMap.h"
+
+namespace mozilla::dom {
+
+class ClientInfo;
+class FetchServicePromises;
+class InternalRequest;
+class InternalResponse;
+class ServiceWorkerDescriptor;
+
+class FetchParent final : public PFetchParent {
+ friend class PFetchParent;
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FetchParent, override);
+
+ mozilla::ipc::IPCResult RecvFetchOp(FetchOpArgs&& aArgs);
+
+ mozilla::ipc::IPCResult RecvAbortFetchOp();
+
+ FetchParent();
+
+ static RefPtr<FetchParent> GetActorByID(const nsID& aID);
+
+ void OnResponseAvailableInternal(SafeRefPtr<InternalResponse>&& aResponse);
+
+ void OnResponseEnd(const ResponseEndArgs& aArgs);
+
+ void OnDataAvailable();
+
+ void OnFlushConsoleReport(
+ const nsTArray<net::ConsoleReportCollected>& aReports);
+
+ class FetchParentCSPEventListener final : public nsICSPEventListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICSPEVENTLISTENER
+
+ FetchParentCSPEventListener(const nsID& aActorID,
+ nsCOMPtr<nsISerialEventTarget> aEventTarget);
+
+ private:
+ ~FetchParentCSPEventListener() = default;
+
+ nsID mActorID;
+ nsCOMPtr<nsISerialEventTarget> mEventTarget;
+ };
+
+ nsICSPEventListener* GetCSPEventListener();
+
+ void OnCSPViolationEvent(const nsAString& aJSON);
+
+ void OnReportPerformanceTiming(const ResponseTiming&& aTiming);
+
+ void OnNotifyNetworkMonitorAlternateStack(uint64_t aChannelID);
+
+ private:
+ ~FetchParent();
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ // The map of FetchParent and ID. Should only access in background thread.
+ static nsTHashMap<nsIDHashKey, RefPtr<FetchParent>> sActorTable;
+
+ // The unique ID of the FetchParent
+ nsID mID;
+ SafeRefPtr<InternalRequest> mRequest;
+ RefPtr<FetchServicePromises> mResponsePromises;
+ RefPtr<GenericPromise::Private> mPromise;
+ PrincipalInfo mPrincipalInfo;
+ nsCString mWorkerScript;
+ Maybe<ClientInfo> mClientInfo;
+ Maybe<ServiceWorkerDescriptor> mController;
+ Maybe<CookieJarSettingsArgs> mCookieJarSettings;
+ nsCOMPtr<nsICSPEventListener> mCSPEventListener;
+ bool mNeedOnDataAvailable{false};
+ bool mHasCSPEventListener{false};
+ bool mExtendForCSPEventListener{false};
+ uint64_t mAssociatedBrowsingContextID{0};
+
+ Atomic<bool> mIsDone{false};
+ Atomic<bool> mActorDestroyed{false};
+
+ nsCOMPtr<nsISerialEventTarget> mBackgroundEventTarget;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/fetch/FetchService.cpp b/dom/fetch/FetchService.cpp
new file mode 100644
index 0000000000..e32d6f178e
--- /dev/null
+++ b/dom/fetch/FetchService.cpp
@@ -0,0 +1,643 @@
+/* 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 "FetchLog.h"
+#include "FetchParent.h"
+#include "nsContentUtils.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsICookieJarSettings.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIIOService.h"
+#include "nsIObserverService.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/FetchService.h"
+#include "mozilla/dom/InternalRequest.h"
+#include "mozilla/dom/InternalResponse.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/dom/PerformanceTiming.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/net/CookieJarSettings.h"
+
+namespace mozilla::dom {
+
+mozilla::LazyLogModule gFetchLog("Fetch");
+
+// FetchServicePromises
+
+FetchServicePromises::FetchServicePromises()
+ : mAvailablePromise(
+ MakeRefPtr<FetchServiceResponseAvailablePromise::Private>(__func__)),
+ mTimingPromise(
+ MakeRefPtr<FetchServiceResponseTimingPromise::Private>(__func__)),
+ mEndPromise(
+ MakeRefPtr<FetchServiceResponseEndPromise::Private>(__func__)) {
+ mAvailablePromise->UseSynchronousTaskDispatch(__func__);
+ mTimingPromise->UseSynchronousTaskDispatch(__func__);
+ mEndPromise->UseSynchronousTaskDispatch(__func__);
+}
+
+RefPtr<FetchServiceResponseAvailablePromise>
+FetchServicePromises::GetResponseAvailablePromise() {
+ return mAvailablePromise;
+}
+
+RefPtr<FetchServiceResponseTimingPromise>
+FetchServicePromises::GetResponseTimingPromise() {
+ return mTimingPromise;
+}
+
+RefPtr<FetchServiceResponseEndPromise>
+FetchServicePromises::GetResponseEndPromise() {
+ return mEndPromise;
+}
+
+void FetchServicePromises::ResolveResponseAvailablePromise(
+ FetchServiceResponse&& aResponse, const char* aMethodName) {
+ if (mAvailablePromise) {
+ mAvailablePromise->Resolve(std::move(aResponse), aMethodName);
+ }
+}
+
+void FetchServicePromises::RejectResponseAvailablePromise(
+ const CopyableErrorResult&& aError, const char* aMethodName) {
+ if (mAvailablePromise) {
+ mAvailablePromise->Reject(aError, aMethodName);
+ }
+}
+
+void FetchServicePromises::ResolveResponseTimingPromise(
+ ResponseTiming&& aTiming, const char* aMethodName) {
+ if (mTimingPromise) {
+ mTimingPromise->Resolve(std::move(aTiming), aMethodName);
+ }
+}
+
+void FetchServicePromises::RejectResponseTimingPromise(
+ const CopyableErrorResult&& aError, const char* aMethodName) {
+ if (mTimingPromise) {
+ mTimingPromise->Reject(aError, aMethodName);
+ }
+}
+
+void FetchServicePromises::ResolveResponseEndPromise(ResponseEndArgs&& aArgs,
+ const char* aMethodName) {
+ if (mEndPromise) {
+ mEndPromise->Resolve(std::move(aArgs), aMethodName);
+ }
+}
+
+void FetchServicePromises::RejectResponseEndPromise(
+ const CopyableErrorResult&& aError, const char* aMethodName) {
+ if (mEndPromise) {
+ mEndPromise->Reject(aError, aMethodName);
+ }
+}
+
+// FetchInstance
+
+nsresult FetchService::FetchInstance::Initialize(FetchArgs&& aArgs) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!aArgs.is<UnknownArgs>() && mArgs.is<UnknownArgs>());
+
+ mArgs = std::move(aArgs);
+
+ // Get needed information for FetchDriver from passed-in channel.
+ if (mArgs.is<NavigationPreloadArgs>()) {
+ mRequest = mArgs.as<NavigationPreloadArgs>().mRequest.clonePtr();
+ nsIChannel* channel = mArgs.as<NavigationPreloadArgs>().mChannel;
+ FETCH_LOG(("FetchInstance::Initialize [%p] request[%p], channel[%p]", this,
+ mRequest.unsafeGetRawPtr(), channel));
+
+ nsresult rv;
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ nsCOMPtr<nsIURI> channelURI;
+ rv = channel->GetURI(getter_AddRefs(channelURI));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsIScriptSecurityManager* securityManager =
+ nsContentUtils::GetSecurityManager();
+ if (securityManager) {
+ securityManager->GetChannelResultPrincipal(channel,
+ getter_AddRefs(mPrincipal));
+ }
+
+ if (!mPrincipal) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Get loadGroup from channel
+ rv = channel->GetLoadGroup(getter_AddRefs(mLoadGroup));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (!mLoadGroup) {
+ rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), mPrincipal);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Get CookieJarSettings from channel
+ rv = loadInfo->GetCookieJarSettings(getter_AddRefs(mCookieJarSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Get PerformanceStorage from channel
+ mPerformanceStorage = loadInfo->GetPerformanceStorage();
+ } else {
+ mIsWorkerFetch = true;
+ mRequest = mArgs.as<WorkerFetchArgs>().mRequest.clonePtr();
+
+ FETCH_LOG(("FetchInstance::Initialize [%p] request[%p]", this,
+ mRequest.unsafeGetRawPtr()));
+
+ auto principalOrErr =
+ PrincipalInfoToPrincipal(mArgs.as<WorkerFetchArgs>().mPrincipalInfo);
+ if (principalOrErr.isErr()) {
+ return principalOrErr.unwrapErr();
+ }
+ mPrincipal = principalOrErr.unwrap();
+ nsresult rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), mPrincipal);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mArgs.as<WorkerFetchArgs>().mCookieJarSettings.isSome()) {
+ net::CookieJarSettings::Deserialize(
+ mArgs.as<WorkerFetchArgs>().mCookieJarSettings.ref(),
+ getter_AddRefs(mCookieJarSettings));
+ }
+ }
+
+ return NS_OK;
+}
+
+RefPtr<FetchServicePromises> FetchService::FetchInstance::Fetch() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_ASSERT(mPrincipal);
+ MOZ_ASSERT(mLoadGroup);
+
+ nsAutoCString principalSpec;
+ MOZ_ALWAYS_SUCCEEDS(mPrincipal->GetAsciiSpec(principalSpec));
+ nsAutoCString requestURL;
+ mRequest->GetURL(requestURL);
+ FETCH_LOG(("FetchInstance::Fetch [%p], mRequest URL: %s mPrincipal: %s", this,
+ requestURL.BeginReading(), principalSpec.BeginReading()));
+
+ nsresult rv;
+
+ // Create a FetchDriver instance
+ mFetchDriver = MakeRefPtr<FetchDriver>(
+ mRequest.clonePtr(), // Fetch Request
+ mPrincipal, // Principal
+ mLoadGroup, // LoadGroup
+ GetMainThreadSerialEventTarget(), // MainThreadEventTarget
+ mCookieJarSettings, // CookieJarSettings
+ mPerformanceStorage, // PerformanceStorage
+ false // IsTrackingFetch
+ );
+
+ if (mIsWorkerFetch) {
+ auto& args = mArgs.as<WorkerFetchArgs>();
+ mFetchDriver->SetWorkerScript(args.mWorkerScript);
+ MOZ_ASSERT(args.mClientInfo.isSome());
+ mFetchDriver->SetClientInfo(args.mClientInfo.ref());
+ mFetchDriver->SetController(args.mController);
+ if (args.mCSPEventListener) {
+ mFetchDriver->SetCSPEventListener(args.mCSPEventListener);
+ }
+ mFetchDriver->SetAssociatedBrowsingContextID(
+ args.mAssociatedBrowsingContextID);
+ }
+
+ mFetchDriver->EnableNetworkInterceptControl();
+
+ mPromises = MakeRefPtr<FetchServicePromises>();
+
+ // Call FetchDriver::Fetch to start fetching.
+ // Pass AbortSignalImpl as nullptr since we no need support AbortSignalImpl
+ // with FetchService. AbortSignalImpl related information should be passed
+ // through PFetch or InterceptedHttpChannel, then call
+ // FetchService::CancelFetch() to abort the running fetch.
+ rv = mFetchDriver->Fetch(nullptr, this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FETCH_LOG(
+ ("FetchInstance::Fetch FetchDriver::Fetch failed(0x%X)", (uint32_t)rv));
+ return FetchService::NetworkErrorResponse(rv);
+ }
+
+ return mPromises;
+}
+
+void FetchService::FetchInstance::Cancel() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ FETCH_LOG(("FetchInstance::Cancel() [%p]", this));
+
+ if (mFetchDriver) {
+ mFetchDriver->RunAbortAlgorithm();
+ }
+
+ MOZ_ASSERT(mPromises);
+
+ mPromises->ResolveResponseAvailablePromise(
+ InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR), __func__);
+
+ mPromises->ResolveResponseTimingPromise(ResponseTiming(), __func__);
+
+ mPromises->ResolveResponseEndPromise(
+ ResponseEndArgs(FetchDriverObserver::eAborted), __func__);
+}
+
+void FetchService::FetchInstance::OnResponseEnd(
+ FetchDriverObserver::EndReason aReason,
+ JS::Handle<JS::Value> aReasonDetails) {
+ FETCH_LOG(("FetchInstance::OnResponseEnd [%p] %s", this,
+ aReason == eAborted ? "eAborted" : "eNetworking"));
+
+ if (mIsWorkerFetch) {
+ FlushConsoleReport();
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [endArgs = ResponseEndArgs(aReason),
+ actorID = mArgs.as<WorkerFetchArgs>().mActorID]() {
+ FETCH_LOG(("FetchInstance::OnResponseEnd, Runnable"));
+ RefPtr<FetchParent> actor = FetchParent::GetActorByID(actorID);
+ if (actor) {
+ actor->OnResponseEnd(std::move(endArgs));
+ }
+ });
+ MOZ_ALWAYS_SUCCEEDS(mArgs.as<WorkerFetchArgs>().mEventTarget->Dispatch(
+ r, nsIThread::DISPATCH_NORMAL));
+ }
+
+ MOZ_ASSERT(mPromises);
+
+ // If ResponseTimingPromise is not resolved, it means the fetch is aborted.
+ // Resolving ResponseTimingPromise with an emtpy ResponseTiming.
+ if (!mPromises->GetResponseTimingPromise()->IsResolved()) {
+ mPromises->ResolveResponseTimingPromise(ResponseTiming(), __func__);
+ }
+ // Resolve the ResponseEndPromise
+ mPromises->ResolveResponseEndPromise(ResponseEndArgs(aReason), __func__);
+
+ if (aReason == eAborted) {
+ return;
+ }
+
+ // Remove the FetchInstance from FetchInstanceTable
+ RefPtr<FetchService> fetchService = FetchService::GetInstance();
+ MOZ_ASSERT(fetchService);
+ auto entry = fetchService->mFetchInstanceTable.Lookup(mPromises);
+ if (entry) {
+ entry.Remove();
+ FETCH_LOG(
+ ("FetchInstance::OnResponseEnd entry of responsePromise[%p] is "
+ "removed",
+ mPromises.get()));
+ }
+}
+
+void FetchService::FetchInstance::OnResponseAvailableInternal(
+ SafeRefPtr<InternalResponse> aResponse) {
+ FETCH_LOG(("FetchInstance::OnResponseAvailableInternal [%p]", this));
+ mResponse = std::move(aResponse);
+
+ nsCOMPtr<nsIInputStream> body;
+ mResponse->GetUnfilteredBody(getter_AddRefs(body));
+ FETCH_LOG(
+ ("FetchInstance::OnResponseAvailableInternal [%p] response body: %p",
+ this, body.get()));
+
+ if (mIsWorkerFetch) {
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [response = mResponse.clonePtr(),
+ actorID = mArgs.as<WorkerFetchArgs>().mActorID]() mutable {
+ FETCH_LOG(("FetchInstance::OnResponseAvailableInternal Runnable"));
+ RefPtr<FetchParent> actor = FetchParent::GetActorByID(actorID);
+ if (actor) {
+ actor->OnResponseAvailableInternal(std::move(response));
+ }
+ });
+ MOZ_ALWAYS_SUCCEEDS(mArgs.as<WorkerFetchArgs>().mEventTarget->Dispatch(
+ r, nsIThread::DISPATCH_NORMAL));
+ }
+
+ MOZ_ASSERT(mPromises);
+
+ // Resolve the ResponseAvailablePromise
+ mPromises->ResolveResponseAvailablePromise(mResponse.clonePtr(), __func__);
+}
+
+bool FetchService::FetchInstance::NeedOnDataAvailable() {
+ if (mArgs.is<WorkerFetchArgs>()) {
+ return mArgs.as<WorkerFetchArgs>().mNeedOnDataAvailable;
+ }
+ return false;
+}
+
+void FetchService::FetchInstance::OnDataAvailable() {
+ FETCH_LOG(("FetchInstance::OnDataAvailable [%p]", this));
+
+ if (!NeedOnDataAvailable()) {
+ return;
+ }
+
+ if (mIsWorkerFetch) {
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [actorID = mArgs.as<WorkerFetchArgs>().mActorID]() {
+ FETCH_LOG(("FetchInstance::OnDataAvailable, Runnable"));
+ RefPtr<FetchParent> actor = FetchParent::GetActorByID(actorID);
+ if (actor) {
+ actor->OnDataAvailable();
+ }
+ });
+ MOZ_ALWAYS_SUCCEEDS(mArgs.as<WorkerFetchArgs>().mEventTarget->Dispatch(
+ r, nsIThread::DISPATCH_NORMAL));
+ }
+}
+
+void FetchService::FetchInstance::FlushConsoleReport() {
+ FETCH_LOG(("FetchInstance::FlushConsoleReport [%p]", this));
+
+ if (mIsWorkerFetch) {
+ if (!mReporter) {
+ return;
+ }
+ nsTArray<net::ConsoleReportCollected> reports;
+ mReporter->StealConsoleReports(reports);
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [actorID = mArgs.as<WorkerFetchArgs>().mActorID,
+ consoleReports = std::move(reports)]() {
+ FETCH_LOG(("FetchInstance::FlushConsolReport, Runnable"));
+ RefPtr<FetchParent> actor = FetchParent::GetActorByID(actorID);
+ if (actor) {
+ actor->OnFlushConsoleReport(std::move(consoleReports));
+ }
+ });
+ MOZ_ALWAYS_SUCCEEDS(mArgs.as<WorkerFetchArgs>().mEventTarget->Dispatch(
+ r, nsIThread::DISPATCH_NORMAL));
+ }
+}
+
+void FetchService::FetchInstance::OnReportPerformanceTiming() {
+ FETCH_LOG(("FetchInstance::OnReportPerformanceTiming [%p]", this));
+ MOZ_ASSERT(mFetchDriver);
+ MOZ_ASSERT(mPromises);
+
+ if (mPromises->GetResponseTimingPromise()->IsResolved()) {
+ return;
+ }
+
+ ResponseTiming timing;
+ UniquePtr<PerformanceTimingData> performanceTiming(
+ mFetchDriver->GetPerformanceTimingData(timing.initiatorType(),
+ timing.entryName()));
+ if (!performanceTiming) {
+ return;
+ }
+ timing.timingData() = performanceTiming->ToIPC();
+ // Force replace initiatorType for ServiceWorkerNavgationPreload.
+ if (!mIsWorkerFetch) {
+ timing.initiatorType() = u"navigation"_ns;
+ } else {
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__,
+ [actorID = mArgs.as<WorkerFetchArgs>().mActorID, timing = timing]() {
+ FETCH_LOG(("FetchInstance::OnReportPerformanceTiming, Runnable"));
+ RefPtr<FetchParent> actor = FetchParent::GetActorByID(actorID);
+ if (actor) {
+ actor->OnReportPerformanceTiming(std::move(timing));
+ }
+ });
+ MOZ_ALWAYS_SUCCEEDS(mArgs.as<WorkerFetchArgs>().mEventTarget->Dispatch(
+ r, nsIThread::DISPATCH_NORMAL));
+ }
+
+ mPromises->ResolveResponseTimingPromise(std::move(timing), __func__);
+}
+
+void FetchService::FetchInstance::OnNotifyNetworkMonitorAlternateStack(
+ uint64_t aChannelID) {
+ FETCH_LOG(("FetchInstance::OnNotifyNetworkMonitorAlternateStack [%p]", this));
+ MOZ_ASSERT(mFetchDriver);
+ MOZ_ASSERT(mPromises);
+ if (!mIsWorkerFetch) {
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [actorID = mArgs.as<WorkerFetchArgs>().mActorID,
+ channelID = aChannelID]() {
+ FETCH_LOG(
+ ("FetchInstance::NotifyNetworkMonitorAlternateStack, Runnable"));
+ RefPtr<FetchParent> actor = FetchParent::GetActorByID(actorID);
+ if (actor) {
+ actor->OnNotifyNetworkMonitorAlternateStack(channelID);
+ }
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(mArgs.as<WorkerFetchArgs>().mEventTarget->Dispatch(
+ r, nsIThread::DISPATCH_NORMAL));
+}
+
+// FetchService
+
+NS_IMPL_ISUPPORTS(FetchService, nsIObserver)
+
+StaticRefPtr<FetchService> gInstance;
+
+/*static*/
+already_AddRefed<FetchService> FetchService::GetInstance() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gInstance) {
+ gInstance = MakeRefPtr<FetchService>();
+ nsresult rv = gInstance->RegisterNetworkObserver();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ gInstance = nullptr;
+ return nullptr;
+ }
+ ClearOnShutdown(&gInstance);
+ }
+ RefPtr<FetchService> service = gInstance;
+ return service.forget();
+}
+
+/*static*/
+RefPtr<FetchServicePromises> FetchService::NetworkErrorResponse(nsresult aRv) {
+ RefPtr<FetchServicePromises> promises = MakeRefPtr<FetchServicePromises>();
+ promises->ResolveResponseAvailablePromise(InternalResponse::NetworkError(aRv),
+ __func__);
+ promises->ResolveResponseTimingPromise(ResponseTiming(), __func__);
+ promises->ResolveResponseEndPromise(
+ ResponseEndArgs(FetchDriverObserver::eAborted), __func__);
+ return promises;
+}
+
+FetchService::FetchService() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+FetchService::~FetchService() {
+ MOZ_ALWAYS_SUCCEEDS(UnregisterNetworkObserver());
+}
+
+nsresult FetchService::RegisterNetworkObserver() {
+ AssertIsOnMainThread();
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (!observerService) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIIOService> ioService = services::GetIOService();
+ if (!ioService) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = observerService->AddObserver(
+ this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->AddObserver(this, "xpcom-shutdown", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ioService->GetOffline(&mOffline);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mObservingNetwork = true;
+
+ return NS_OK;
+}
+
+nsresult FetchService::UnregisterNetworkObserver() {
+ AssertIsOnMainThread();
+ nsresult rv;
+ if (mObservingNetwork) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ rv = observerService->RemoveObserver(this,
+ NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = observerService->RemoveObserver(this, "xpcom-shutdown");
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mObservingNetwork = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP FetchService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ FETCH_LOG(("FetchService::Observe topic: %s", aTopic));
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) ||
+ !strcmp(aTopic, "xpcom-shutdown"));
+
+ if (!strcmp(aTopic, "xpcom-shutdown")) {
+ // Going to shutdown, unregister the network status observer to avoid
+ // receiving
+ nsresult rv = UnregisterNetworkObserver();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ if (nsDependentString(aData).EqualsLiteral(NS_IOSERVICE_ONLINE)) {
+ mOffline = false;
+ } else {
+ mOffline = true;
+ // Network is offline, cancel running fetchs.
+ for (auto it = mFetchInstanceTable.begin(), end = mFetchInstanceTable.end();
+ it != end; ++it) {
+ it->GetData()->Cancel();
+ }
+ mFetchInstanceTable.Clear();
+ }
+ return NS_OK;
+}
+
+RefPtr<FetchServicePromises> FetchService::Fetch(FetchArgs&& aArgs) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ FETCH_LOG(("FetchService::Fetch (%s)", aArgs.is<NavigationPreloadArgs>()
+ ? "NavigationPreload"
+ : "WorkerFetch"));
+ if (mOffline) {
+ FETCH_LOG(("FetchService::Fetch network offline"));
+ return NetworkErrorResponse(NS_ERROR_OFFLINE);
+ }
+
+ // Create FetchInstance
+ RefPtr<FetchInstance> fetch = MakeRefPtr<FetchInstance>();
+
+ // Call FetchInstance::Initialize() to get needed information for FetchDriver,
+ nsresult rv = fetch->Initialize(std::move(aArgs));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NetworkErrorResponse(rv);
+ }
+
+ // Call FetchInstance::Fetch() to start an asynchronous fetching.
+ RefPtr<FetchServicePromises> promises = fetch->Fetch();
+ MOZ_ASSERT(promises);
+
+ if (!promises->GetResponseAvailablePromise()->IsResolved()) {
+ // Insert the created FetchInstance into FetchInstanceTable.
+ if (!mFetchInstanceTable.WithEntryHandle(promises, [&](auto&& entry) {
+ if (entry.HasEntry()) {
+ return false;
+ }
+ entry.Insert(fetch);
+ return true;
+ })) {
+ FETCH_LOG(
+ ("FetchService::Fetch entry[%p] already exists", promises.get()));
+ return NetworkErrorResponse(NS_ERROR_UNEXPECTED);
+ }
+ FETCH_LOG(("FetchService::Fetch entry[%p] of FetchInstance[%p] added",
+ promises.get(), fetch.get()));
+ }
+ return promises;
+}
+
+void FetchService::CancelFetch(const RefPtr<FetchServicePromises>&& aPromises) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPromises);
+ FETCH_LOG(("FetchService::CancelFetch aPromises[%p]", aPromises.get()));
+
+ auto entry = mFetchInstanceTable.Lookup(aPromises);
+ if (entry) {
+ // Notice any modifications here before entry.Remove() probably should be
+ // reflected to Observe() offline case.
+ entry.Data()->Cancel();
+ entry.Remove();
+ FETCH_LOG(
+ ("FetchService::CancelFetch entry [%p] removed", aPromises.get()));
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/FetchService.h b/dom/fetch/FetchService.h
new file mode 100644
index 0000000000..3032c4d2a9
--- /dev/null
+++ b/dom/fetch/FetchService.h
@@ -0,0 +1,186 @@
+/* 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_FetchService_h
+#define _mozilla_dom_FetchService_h
+
+#include "nsIChannel.h"
+#include "nsIObserver.h"
+#include "nsTHashMap.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/FetchDriver.h"
+#include "mozilla/dom/FetchTypes.h"
+#include "mozilla/dom/PerformanceTimingTypes.h"
+#include "mozilla/dom/SafeRefPtr.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/net/NeckoChannelParams.h"
+
+class nsILoadGroup;
+class nsIPrincipal;
+class nsICookieJarSettings;
+class PerformanceStorage;
+
+namespace mozilla::dom {
+
+class InternalRequest;
+class InternalResponse;
+class ClientInfo;
+class ServiceWorkerDescriptor;
+
+using FetchServiceResponse = SafeRefPtr<InternalResponse>;
+using FetchServiceResponseAvailablePromise =
+ MozPromise<FetchServiceResponse, CopyableErrorResult, true>;
+using FetchServiceResponseTimingPromise =
+ MozPromise<ResponseTiming, CopyableErrorResult, true>;
+using FetchServiceResponseEndPromise =
+ MozPromise<ResponseEndArgs, CopyableErrorResult, true>;
+
+class FetchServicePromises final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FetchServicePromises);
+
+ public:
+ FetchServicePromises();
+
+ RefPtr<FetchServiceResponseAvailablePromise> GetResponseAvailablePromise();
+ RefPtr<FetchServiceResponseTimingPromise> GetResponseTimingPromise();
+ RefPtr<FetchServiceResponseEndPromise> GetResponseEndPromise();
+
+ void ResolveResponseAvailablePromise(FetchServiceResponse&& aResponse,
+ const char* aMethodName);
+ void RejectResponseAvailablePromise(const CopyableErrorResult&& aError,
+ const char* aMethodName);
+ void ResolveResponseTimingPromise(ResponseTiming&& aTiming,
+ const char* aMethodName);
+ void RejectResponseTimingPromise(const CopyableErrorResult&& aError,
+ const char* aMethodName);
+ void ResolveResponseEndPromise(ResponseEndArgs&& aArgs,
+ const char* aMethodName);
+ void RejectResponseEndPromise(const CopyableErrorResult&& aError,
+ const char* aMethodName);
+
+ private:
+ ~FetchServicePromises() = default;
+
+ RefPtr<FetchServiceResponseAvailablePromise::Private> mAvailablePromise;
+ RefPtr<FetchServiceResponseTimingPromise::Private> mTimingPromise;
+ RefPtr<FetchServiceResponseEndPromise::Private> mEndPromise;
+};
+
+/**
+ * FetchService is a singleton object which designed to be used in parent
+ * process main thread only. It is used to handle the special fetch requests
+ * from ServiceWorkers(by Navigation Preload) and PFetch.
+ *
+ * FetchService creates FetchInstance internally to represent each Fetch
+ * request. It supports an asynchronous fetching, FetchServicePromises is
+ * created when a Fetch starts, once the response is ready or any error happens,
+ * the FetchServicePromises would be resolved or rejected. The promises
+ * consumers can set callbacks to handle the Fetch result.
+ */
+class FetchService final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ struct NavigationPreloadArgs {
+ SafeRefPtr<InternalRequest> mRequest;
+ nsCOMPtr<nsIChannel> mChannel;
+ };
+
+ struct WorkerFetchArgs {
+ SafeRefPtr<InternalRequest> mRequest;
+ mozilla::ipc::PrincipalInfo mPrincipalInfo;
+ nsCString mWorkerScript;
+ Maybe<ClientInfo> mClientInfo;
+ Maybe<ServiceWorkerDescriptor> mController;
+ Maybe<net::CookieJarSettingsArgs> mCookieJarSettings;
+ bool mNeedOnDataAvailable;
+ nsCOMPtr<nsICSPEventListener> mCSPEventListener;
+ uint64_t mAssociatedBrowsingContextID;
+ nsCOMPtr<nsISerialEventTarget> mEventTarget;
+ nsID mActorID;
+ };
+
+ struct UnknownArgs {};
+
+ using FetchArgs =
+ Variant<NavigationPreloadArgs, WorkerFetchArgs, UnknownArgs>;
+
+ static already_AddRefed<FetchService> GetInstance();
+
+ static RefPtr<FetchServicePromises> NetworkErrorResponse(nsresult aRv);
+
+ FetchService();
+
+ // This method creates a FetchInstance to trigger fetch.
+ // The created FetchInstance is saved in mFetchInstanceTable
+ RefPtr<FetchServicePromises> Fetch(FetchArgs&& aArgs);
+
+ void CancelFetch(const RefPtr<FetchServicePromises>&& aPromises);
+
+ private:
+ /**
+ * FetchInstance is an internal representation for each Fetch created by
+ * FetchService.
+ * FetchInstance is also a FetchDriverObserver which has responsibility to
+ * resolve/reject the FetchServicePromises.
+ * FetchInstance triggers fetch by instancing a FetchDriver with proper
+ * initialization. The general usage flow of FetchInstance is as follows
+ *
+ * RefPtr<FetchInstance> fetch = MakeRefPtr<FetchInstance>();
+ * fetch->Initialize(FetchArgs args);
+ * RefPtr<FetchServicePromises> fetch->Fetch();
+ */
+ class FetchInstance final : public FetchDriverObserver {
+ public:
+ FetchInstance() = default;
+
+ nsresult Initialize(FetchArgs&& aArgs);
+
+ RefPtr<FetchServicePromises> Fetch();
+
+ void Cancel();
+
+ /* FetchDriverObserver interface */
+ void OnResponseEnd(FetchDriverObserver::EndReason aReason,
+ JS::Handle<JS::Value> aReasonDetails) override;
+ void OnResponseAvailableInternal(
+ SafeRefPtr<InternalResponse> aResponse) override;
+ bool NeedOnDataAvailable() override;
+ void OnDataAvailable() override;
+ void FlushConsoleReport() override;
+ void OnReportPerformanceTiming() override;
+ void OnNotifyNetworkMonitorAlternateStack(uint64_t aChannelID) override;
+
+ private:
+ ~FetchInstance() = default;
+
+ SafeRefPtr<InternalRequest> mRequest;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+ RefPtr<PerformanceStorage> mPerformanceStorage;
+ FetchArgs mArgs{AsVariant(FetchService::UnknownArgs())};
+ RefPtr<FetchDriver> mFetchDriver;
+ SafeRefPtr<InternalResponse> mResponse;
+ RefPtr<FetchServicePromises> mPromises;
+ bool mIsWorkerFetch{false};
+ };
+
+ ~FetchService();
+
+ nsresult RegisterNetworkObserver();
+ nsresult UnregisterNetworkObserver();
+
+ // This is a container to manage the generated fetches.
+ nsTHashMap<nsRefPtrHashKey<FetchServicePromises>, RefPtr<FetchInstance> >
+ mFetchInstanceTable;
+ bool mObservingNetwork{false};
+ bool mOffline{false};
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_FetchService_h
diff --git a/dom/fetch/FetchStreamReader.cpp b/dom/fetch/FetchStreamReader.cpp
new file mode 100644
index 0000000000..de5a2cfefe
--- /dev/null
+++ b/dom/fetch/FetchStreamReader.cpp
@@ -0,0 +1,441 @@
+/* -*- 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 "mozilla/ConsoleReportCollector.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseBinding.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamDefaultController.h"
+#include "mozilla/dom/ReadableStreamDefaultReader.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/TaskCategory.h"
+#include "nsContentUtils.h"
+#include "nsDebug.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(mReader)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FetchStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReader)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(FetchStreamReader)
+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;
+
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(streamReader->mPipeOut),
+ true, true, 0, 0);
+
+ if (!NS_IsMainThread()) {
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+ workerPrivate, "FetchStreamReader", [streamReader]() {
+ MOZ_ASSERT(streamReader);
+
+ // mAsyncWaitWorkerRef may keep the (same) StrongWorkerRef alive even
+ // when mWorkerRef has already been nulled out by a previous call to
+ // CloseAndRelease, we can just safely ignore this callback then
+ // (as would the CloseAndRelease do on a second call).
+ if (streamReader->mWorkerRef) {
+ streamReader->CloseAndRelease(
+ streamReader->mWorkerRef->Private()->GetJSContext(),
+ NS_ERROR_DOM_INVALID_STATE_ERR);
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(streamReader->mAsyncWaitWorkerRef);
+ }
+ });
+
+ 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)) {
+ 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) {
+ ErrorResult rv;
+ if (aStatus == NS_ERROR_DOM_WRONG_TYPE_ERR) {
+ rv.ThrowTypeError<MSG_FETCH_BODY_WRONG_TYPE>();
+ } else {
+ rv = aStatus;
+ }
+ JS::Rooted<JS::Value> errorValue(aCx);
+ if (ToJSValue(aCx, std::move(rv), &errorValue)) {
+ IgnoredErrorResult ignoredError;
+ // 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.
+ RefPtr<Promise> cancelResultPromise =
+ MOZ_KnownLive(mReader)->Cancel(aCx, errorValue, ignoredError);
+ NS_WARNING_ASSERTION(!ignoredError.Failed(),
+ "Failed to cancel stream during close and release");
+ if (cancelResultPromise) {
+ bool setHandled = cancelResultPromise->SetAnyPromiseIsHandled();
+ NS_WARNING_ASSERTION(setHandled,
+ "Failed to mark cancel promise as handled.");
+ (void)setHandled;
+ }
+ }
+
+ // We don't want to propagate exceptions during the cleanup.
+ JS_ClearPendingException(aCx);
+ }
+
+ mStreamClosed = true;
+
+ mGlobal = nullptr;
+
+ if (mPipeOut) {
+ mPipeOut->CloseWithStatus(aStatus);
+ }
+ mPipeOut = nullptr;
+
+ mWorkerRef = nullptr;
+
+ mReader = nullptr;
+ mBuffer.Clear();
+}
+
+// https://fetch.spec.whatwg.org/#body-incrementally-read
+void FetchStreamReader::StartConsuming(JSContext* aCx, ReadableStream* aStream,
+ ErrorResult& aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(!mReader);
+ MOZ_DIAGNOSTIC_ASSERT(aStream);
+
+ // Step 2: Let reader be the result of getting a reader for body’s stream.
+ RefPtr<ReadableStreamDefaultReader> reader = aStream->GetReader(aRv);
+ if (aRv.Failed()) {
+ CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ mReader = reader;
+
+ mAsyncWaitWorkerRef = mWorkerRef;
+ aRv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
+ if (NS_WARN_IF(aRv.Failed())) {
+ mAsyncWaitWorkerRef = nullptr;
+ CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+struct FetchReadRequest : public ReadRequest {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FetchReadRequest, ReadRequest)
+
+ explicit FetchReadRequest(FetchStreamReader* aReader)
+ : mFetchStreamReader(aReader) {}
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) override {
+ mFetchStreamReader->ChunkSteps(aCx, aChunk, aRv);
+ }
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void CloseSteps(JSContext* aCx, ErrorResult& aRv) override {
+ mFetchStreamReader->CloseSteps(aCx, aRv);
+ }
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) override {
+ mFetchStreamReader->ErrorSteps(aCx, aError, aRv);
+ }
+
+ protected:
+ virtual ~FetchReadRequest() = default;
+
+ MOZ_KNOWN_LIVE RefPtr<FetchStreamReader> mFetchStreamReader;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(FetchReadRequest, ReadRequest,
+ mFetchStreamReader)
+NS_IMPL_ADDREF_INHERITED(FetchReadRequest, ReadRequest)
+NS_IMPL_RELEASE_INHERITED(FetchReadRequest, ReadRequest)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchReadRequest)
+NS_INTERFACE_MAP_END_INHERITING(ReadRequest)
+
+// nsIOutputStreamCallback interface
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+NS_IMETHODIMP
+FetchStreamReader::OnOutputStreamReady(nsIAsyncOutputStream* aStream) {
+ NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
+ if (mStreamClosed) {
+ mAsyncWaitWorkerRef = nullptr;
+ return NS_OK;
+ }
+
+ AutoEntryScript aes(mGlobal, "ReadableStreamReader.read", !mWorkerRef);
+ if (!Process(aes.cx())) {
+ // We're done processing data, and haven't queued up a new AsyncWait - we
+ // can clear our mAsyncWaitWorkerRef.
+ mAsyncWaitWorkerRef = nullptr;
+ }
+ return NS_OK;
+}
+
+bool FetchStreamReader::Process(JSContext* aCx) {
+ NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
+ MOZ_ASSERT(mReader);
+
+ if (!mBuffer.IsEmpty()) {
+ nsresult rv = WriteBuffer();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseAndRelease(aCx, NS_ERROR_DOM_ABORT_ERR);
+ return false;
+ }
+ return true;
+ }
+
+ // Check if the output stream has already been closed. This lets us propagate
+ // errors eagerly, and detect output stream closures even when we have no data
+ // to write.
+ if (NS_WARN_IF(NS_FAILED(mPipeOut->StreamStatus()))) {
+ CloseAndRelease(aCx, NS_ERROR_DOM_ABORT_ERR);
+ return false;
+ }
+
+ // We're waiting on new data - set up a WAIT_CLOSURE_ONLY callback so we
+ // notice if the reader closes.
+ nsresult rv = mPipeOut->AsyncWait(
+ this, nsIAsyncOutputStream::WAIT_CLOSURE_ONLY, 0, mOwningEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return false;
+ }
+
+ // If we already have an outstanding read request, don't start another one
+ // concurrently.
+ if (!mHasOutstandingReadRequest) {
+ // https://fetch.spec.whatwg.org/#incrementally-read-loop
+ // The below very loosely tries to implement the incrementally-read-loop
+ // from the fetch spec.
+ // Step 2: Read a chunk from reader given readRequest.
+ RefPtr<ReadRequest> readRequest = new FetchReadRequest(this);
+ RefPtr<ReadableStreamDefaultReader> reader = mReader;
+ mHasOutstandingReadRequest = true;
+
+ IgnoredErrorResult err;
+ reader->ReadChunk(aCx, *readRequest, err);
+ if (NS_WARN_IF(err.Failed())) {
+ // Let's close the stream.
+ mHasOutstandingReadRequest = false;
+ CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
+ // Don't return false, as we've already called `AsyncWait`.
+ }
+ }
+ return true;
+}
+
+void FetchStreamReader::ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) {
+ // This roughly implements the chunk steps from
+ // https://fetch.spec.whatwg.org/#incrementally-read-loop.
+
+ mHasOutstandingReadRequest = false;
+
+ // Step 2. If chunk is not a Uint8Array object, then set continueAlgorithm to
+ // this step: run processBodyError given a TypeError.
+ RootedSpiderMonkeyInterface<Uint8Array> chunk(aCx);
+ if (!aChunk.isObject() || !chunk.Init(&aChunk.toObject())) {
+ CloseAndRelease(aCx, NS_ERROR_DOM_WRONG_TYPE_ERR);
+ return;
+ }
+ chunk.ComputeState();
+
+ MOZ_DIAGNOSTIC_ASSERT(mBuffer.IsEmpty());
+
+ // Let's take a copy of the data.
+ // FIXME: We could sometimes avoid this copy by trying to write `chunk`
+ // directly into `mPipeOut` eagerly, and only filling `mBuffer` if there isn't
+ // enough space in the pipe's buffer.
+ if (!mBuffer.AppendElements(chunk.Data(), chunk.Length(), fallible)) {
+ CloseAndRelease(aCx, NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ mBufferOffset = 0;
+ mBufferRemaining = chunk.Length();
+
+ nsresult rv = WriteBuffer();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseAndRelease(aCx, NS_ERROR_DOM_ABORT_ERR);
+ }
+}
+
+void FetchStreamReader::CloseSteps(JSContext* aCx, ErrorResult& aRv) {
+ mHasOutstandingReadRequest = false;
+ CloseAndRelease(aCx, NS_BASE_STREAM_CLOSED);
+}
+
+void FetchStreamReader::ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) {
+ mHasOutstandingReadRequest = false;
+ ReportErrorToConsole(aCx, aError);
+ CloseAndRelease(aCx, NS_ERROR_FAILURE);
+}
+
+nsresult FetchStreamReader::WriteBuffer() {
+ MOZ_ASSERT(mBuffer.Length() == (mBufferOffset + mBufferRemaining));
+
+ char* data = reinterpret_cast<char*>(mBuffer.Elements());
+
+ while (mBufferRemaining > 0) {
+ 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::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..65d94d8cb0
--- /dev/null
+++ b/dom/fetch/FetchStreamReader.h
@@ -0,0 +1,94 @@
+/* -*- 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/Attributes.h"
+#include "mozilla/dom/FetchBinding.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla::dom {
+
+class ReadableStream;
+class ReadableStreamDefaultReader;
+class StrongWorkerRef;
+
+class FetchStreamReader final : public nsIOutputStreamCallback {
+ 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);
+
+ MOZ_CAN_RUN_SCRIPT
+ void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ void CloseSteps(JSContext* aCx, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv);
+
+ // 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.
+ //
+ // This is a script boundary minimize annotation changes required while
+ // we figure out how to handle some more tricky annotation cases (for
+ // example, the destructor of this class. Tracking under Bug 1750656)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void CloseAndRelease(JSContext* aCx, nsresult aStatus);
+
+ void StartConsuming(JSContext* aCx, ReadableStream* aStream,
+ ErrorResult& aRv);
+
+ private:
+ explicit FetchStreamReader(nsIGlobalObject* aGlobal);
+ ~FetchStreamReader();
+
+ nsresult WriteBuffer();
+
+ // Attempt to copy data from mBuffer into mPipeOut. Returns `true` if data was
+ // written, and AsyncWait callbacks or FetchReadRequest calls have been set up
+ // to write more data in the future, and `false` otherwise.
+ MOZ_CAN_RUN_SCRIPT
+ bool Process(JSContext* aCx);
+
+ void ReportErrorToConsole(JSContext* aCx, JS::Handle<JS::Value> aValue);
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
+
+ RefPtr<StrongWorkerRef> mWorkerRef;
+ RefPtr<StrongWorkerRef> mAsyncWaitWorkerRef;
+
+ RefPtr<ReadableStreamDefaultReader> mReader;
+
+ nsTArray<uint8_t> mBuffer;
+ uint32_t mBufferRemaining = 0;
+ uint32_t mBufferOffset = 0;
+
+ bool mHasOutstandingReadRequest = false;
+ bool mStreamClosed = false;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_FetchStreamReader_h
diff --git a/dom/fetch/FetchStreamUtils.cpp b/dom/fetch/FetchStreamUtils.cpp
new file mode 100644
index 0000000000..d9cb762526
--- /dev/null
+++ b/dom/fetch/FetchStreamUtils.cpp
@@ -0,0 +1,83 @@
+/* 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 "FetchStreamUtils.h"
+
+#include "mozilla/NotNull.h"
+#include "mozilla/RemoteLazyInputStreamChild.h"
+#include "mozilla/RemoteLazyInputStreamStorage.h"
+#include "mozilla/dom/FetchTypes.h"
+#include "mozilla/dom/IPCBlob.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "nsContentUtils.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+RefPtr<RemoteLazyInputStreamStorage> GetRemoteLazyInputStreamStorage() {
+ auto storageOrErr = RemoteLazyInputStreamStorage::Get();
+ MOZ_ASSERT(storageOrErr.isOk());
+ return storageOrErr.unwrap();
+}
+
+} // namespace
+
+NotNull<nsCOMPtr<nsIInputStream>> ToInputStream(
+ const ParentToParentStream& aStream) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return WrapNotNull(
+ GetRemoteLazyInputStreamStorage()->ForgetStream(aStream.uuid()));
+}
+
+NotNull<nsCOMPtr<nsIInputStream>> ToInputStream(
+ const ParentToChildStream& aStream) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ nsCOMPtr<nsIInputStream> result;
+ if (aStream.type() == ParentToChildStream::TRemoteLazyInputStream) {
+ result = aStream.get_RemoteLazyInputStream();
+ } else {
+ result = DeserializeIPCStream(aStream.get_IPCStream());
+ }
+ return WrapNotNull(result);
+}
+
+ParentToParentStream ToParentToParentStream(
+ const NotNull<nsCOMPtr<nsIInputStream>>& aStream, int64_t aStreamSize) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ ParentToParentStream stream;
+ stream.uuid() = nsID::GenerateUUID();
+ GetRemoteLazyInputStreamStorage()->AddStream(aStream.get(), stream.uuid());
+ return stream;
+}
+
+ParentToChildStream ToParentToChildStream(
+ const NotNull<nsCOMPtr<nsIInputStream>>& aStream, int64_t aStreamSize,
+ NotNull<mozilla::ipc::PBackgroundParent*> aBackgroundParent,
+ bool aSerializeAsLazy) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ ParentToChildStream result;
+ if (aSerializeAsLazy) {
+ result = RemoteLazyInputStream::WrapStream(aStream.get());
+ } else {
+ nsCOMPtr<nsIInputStream> stream(aStream.get());
+ mozilla::ipc::IPCStream ipcStream;
+ Unused << NS_WARN_IF(
+ !mozilla::ipc::SerializeIPCStream(stream.forget(), ipcStream, false));
+ result = ipcStream;
+ }
+ return result;
+}
+
+ParentToChildStream ToParentToChildStream(
+ const ParentToParentStream& aStream, int64_t aStreamSize,
+ NotNull<mozilla::ipc::PBackgroundParent*> aBackgroundParent) {
+ return ToParentToChildStream(ToInputStream(aStream), aStreamSize,
+ aBackgroundParent);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/FetchStreamUtils.h b/dom/fetch/FetchStreamUtils.h
new file mode 100644
index 0000000000..407339df41
--- /dev/null
+++ b/dom/fetch/FetchStreamUtils.h
@@ -0,0 +1,56 @@
+/* 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_FetchStreamUtils_h
+#define _mozilla_dom_fetch_FetchStreamUtils_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/dom/FetchTypes.h"
+
+#include "nsIInputStream.h"
+
+#include <cstdint>
+
+namespace mozilla {
+
+namespace ipc {
+class PBackgroundParent;
+}
+
+namespace dom {
+
+// Convert a ParentToParentStream received over IPC to an nsIInputStream. Can
+// only be called in the parent process.
+NotNull<nsCOMPtr<nsIInputStream>> ToInputStream(
+ const ParentToParentStream& aStream);
+
+// Convert a ParentToChildStream received over IPC to an nsIInputStream. Can
+// only be called in a content process.
+NotNull<nsCOMPtr<nsIInputStream>> ToInputStream(
+ const ParentToChildStream& aStream);
+
+// Serialize an nsIInputStream for IPC inside the parent process. Can only be
+// called in the parent process.
+ParentToParentStream ToParentToParentStream(
+ const NotNull<nsCOMPtr<nsIInputStream>>& aStream, int64_t aStreamSize);
+
+// Serialize an nsIInputStream for IPC from the parent process to a content
+// process. Can only be called in the parent process.
+ParentToChildStream ToParentToChildStream(
+ const NotNull<nsCOMPtr<nsIInputStream>>& aStream, int64_t aStreamSize,
+ NotNull<mozilla::ipc::PBackgroundParent*> aBackgroundParent,
+ bool aSerializeAsLazy = true);
+
+// Convert a ParentToParentStream to a ParentToChildStream. Can only be called
+// in the parent process.
+ParentToChildStream ToParentToChildStream(
+ const ParentToParentStream& aStream, int64_t aStreamSize,
+ NotNull<mozilla::ipc::PBackgroundParent*> aBackgroundParent);
+
+} // namespace dom
+
+} // namespace mozilla
+
+#endif // _mozilla_dom_fetch_FetchStreamUtils_h
diff --git a/dom/fetch/FetchTypes.ipdlh b/dom/fetch/FetchTypes.ipdlh
new file mode 100644
index 0000000000..f8c594bf1a
--- /dev/null
+++ b/dom/fetch/FetchTypes.ipdlh
@@ -0,0 +1,127 @@
+/* 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 PBackgroundSharedTypes;
+include PerformanceTimingTypes;
+include NeckoChannelParams;
+
+include "mozilla/dom/FetchIPCTypes.h";
+include "mozilla/ipc/TransportSecurityInfoUtils.h";
+
+using mozilla::dom::HeadersGuardEnum from "mozilla/dom/HeadersBinding.h";
+using mozilla::dom::ReferrerPolicy from "mozilla/dom/ReferrerPolicyBinding.h";
+using mozilla::dom::RequestCache from "mozilla/dom/RequestBinding.h";
+using mozilla::dom::RequestCredentials from "mozilla/dom/RequestBinding.h";
+using mozilla::dom::RequestRedirect from "mozilla/dom/RequestBinding.h";
+using mozilla::dom::ResponseType from "mozilla/dom/ResponseBinding.h";
+using struct nsID from "nsID.h";
+using mozilla::dom::FetchDriverObserver::EndReason from "mozilla/dom/FetchDriver.h";
+using nsILoadInfo::CrossOriginEmbedderPolicy from "nsILoadInfo.h";
+[RefCounted] using class mozilla::RemoteLazyInputStream from "mozilla/RemoteLazyInputStream.h";
+[RefCounted] using class nsITransportSecurityInfo from "nsITransportSecurityInfo.h";
+
+namespace mozilla {
+namespace dom {
+
+struct HeadersEntry {
+ nsCString name;
+ nsCString value;
+};
+
+struct ParentToParentStream {
+ // Used as a key for RemoteLazyInputStreamStorage
+ nsID uuid;
+};
+
+union ParentToChildStream {
+ nullable RemoteLazyInputStream;
+ IPCStream;
+};
+
+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;
+ ReferrerPolicy environmentReferrerPolicy;
+ RequestMode requestMode;
+ RequestCredentials requestCredentials;
+ RequestCache cacheMode;
+ RequestRedirect requestRedirect;
+ nsString integrity;
+ nsCString fragment;
+ PrincipalInfo? principalInfo;
+ PrincipalInfo? interceptionTriggeringPrincipalInfo;
+ uint32_t interceptionContentPolicyType;
+ RedirectHistoryEntryInfo[] interceptionRedirectChain;
+ bool interceptionFromThirdParty;
+ CrossOriginEmbedderPolicy embedderPolicy;
+};
+
+struct InternalResponseMetadata {
+ ResponseType type;
+ nsCString[] urlList;
+ uint16_t status;
+ nsCString statusText;
+ HeadersGuardEnum headersGuard;
+ HeadersEntry[] headers;
+ nsresult errorCode;
+ nsCString alternativeDataType;
+ nullable nsITransportSecurityInfo securityInfo;
+ PrincipalInfo? principalInfo;
+ nsCString bodyBlobURISpec;
+ nsString bodyLocalPath;
+ RequestCredentials credentialsMode;
+};
+
+struct ParentToParentInternalResponse {
+ InternalResponseMetadata metadata;
+ ParentToParentStream? body;
+ int64_t bodySize;
+ ParentToParentStream? alternativeBody;
+};
+
+struct ParentToChildInternalResponse {
+ InternalResponseMetadata metadata;
+ ParentToChildStream? body;
+ int64_t bodySize;
+ ParentToChildStream? alternativeBody;
+};
+
+struct ChildToParentInternalResponse {
+ InternalResponseMetadata metadata;
+ ChildToParentStream? body;
+ int64_t bodySize;
+ ChildToParentStream? alternativeBody;
+};
+
+struct ResponseTiming {
+ IPCPerformanceTimingData timingData;
+ nsString initiatorType;
+ nsString entryName;
+};
+
+struct ResponseEndArgs {
+ EndReason endReason;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/dom/fetch/FetchUtil.cpp b/dom/fetch/FetchUtil.cpp
new file mode 100644
index 0000000000..48281df321
--- /dev/null
+++ b/dom/fetch/FetchUtil.cpp
@@ -0,0 +1,814 @@
+/* -*- 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 "zlib.h"
+
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "nsCRT.h"
+#include "nsError.h"
+#include "nsIAsyncInputStream.h"
+#include "nsICloneableInputStream.h"
+#include "nsIHttpChannel.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "js/BuildId.h"
+#include "mozilla/dom/Document.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/InternalRequest.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/ReferrerInfo.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 StoreOptimizedEncodingRunnable final : public Runnable {
+ nsMainThreadPtrHandle<nsICacheInfoChannel> mCache;
+ Vector<uint8_t> mBytes;
+
+ public:
+ StoreOptimizedEncodingRunnable(
+ nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
+ Vector<uint8_t>&& aBytes)
+ : Runnable("StoreOptimizedEncodingRunnable"),
+ mCache(std::move(aCache)),
+ mBytes(std::move(aBytes)) {}
+
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ nsCOMPtr<nsIAsyncOutputStream> stream;
+ rv = mCache->OpenAlternativeOutputStream(FetchUtil::WasmAltDataType,
+ int64_t(mBytes.length()),
+ getter_AddRefs(stream));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ auto closeStream = MakeScopeExit([&]() { stream->CloseWithStatus(rv); });
+
+ uint32_t written;
+ rv = stream->Write((char*)mBytes.begin(), mBytes.length(), &written);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MOZ_RELEASE_ASSERT(mBytes.length() == written);
+ 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();
+ }
+
+ // 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)
+
+inline nsISupports* ToSupports(WindowStreamOwner* aObj) {
+ return static_cast<nsIObserver*>(aObj);
+}
+
+class WorkerStreamOwner final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(WorkerStreamOwner)
+
+ explicit WorkerStreamOwner(nsIAsyncInputStream* aStream,
+ nsCOMPtr<nsIEventTarget>&& target)
+ : mStream(aStream), mOwningEventTarget(std::move(target)) {}
+
+ static already_AddRefed<WorkerStreamOwner> Create(
+ nsIAsyncInputStream* aStream, WorkerPrivate* aWorker,
+ nsCOMPtr<nsIEventTarget>&& target) {
+ RefPtr<WorkerStreamOwner> self =
+ new WorkerStreamOwner(aStream, std::move(target));
+
+ self->mWorkerRef =
+ StrongWorkerRef::Create(aWorker, "JSStreamConsumer", [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 release
+ // proxy.
+ self->mStream->Close();
+ self->mStream = nullptr;
+ }
+ });
+
+ if (!self->mWorkerRef) {
+ return nullptr;
+ }
+
+ return self.forget();
+ }
+
+ static void ProxyRelease(already_AddRefed<WorkerStreamOwner> aDoomed) {
+ RefPtr<WorkerStreamOwner> doomed = aDoomed;
+ nsIEventTarget* target = doomed->mOwningEventTarget;
+ NS_ProxyRelease("WorkerStreamOwner", target, doomed.forget(),
+ /* aAlwaysProxy = */ true);
+ }
+
+ 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<StrongWorkerRef> mWorkerRef;
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+};
+
+class JSStreamConsumer final : public nsIInputStreamCallback,
+ public JS::OptimizedEncodingListener {
+ // A LengthPrefixType is stored at the start of the compressed optimized
+ // encoding, allowing the decompressed buffer to be allocated to exactly
+ // the right size.
+ using LengthPrefixType = uint32_t;
+ static const unsigned PrefixBytes = sizeof(LengthPrefixType);
+
+ RefPtr<WindowStreamOwner> mWindowStreamOwner;
+ RefPtr<WorkerStreamOwner> mWorkerStreamOwner;
+ nsMainThreadPtrHandle<nsICacheInfoChannel> mCache;
+ const bool mOptimizedEncoding;
+ z_stream mZStream;
+ bool mZStreamInitialized;
+ Vector<uint8_t> mOptimizedEncodingBytes;
+ JS::StreamConsumer* mConsumer;
+ bool mConsumerAborted;
+
+ JSStreamConsumer(already_AddRefed<WindowStreamOwner> aWindowStreamOwner,
+ nsIGlobalObject* aGlobal, JS::StreamConsumer* aConsumer,
+ nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
+ bool aOptimizedEncoding)
+ : mWindowStreamOwner(aWindowStreamOwner),
+ mCache(std::move(aCache)),
+ mOptimizedEncoding(aOptimizedEncoding),
+ mZStreamInitialized(false),
+ mConsumer(aConsumer),
+ mConsumerAborted(false) {
+ MOZ_DIAGNOSTIC_ASSERT(mWindowStreamOwner);
+ MOZ_DIAGNOSTIC_ASSERT(mConsumer);
+ }
+
+ JSStreamConsumer(RefPtr<WorkerStreamOwner> aWorkerStreamOwner,
+ nsIGlobalObject* aGlobal, JS::StreamConsumer* aConsumer,
+ nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
+ bool aOptimizedEncoding)
+ : mWorkerStreamOwner(std::move(aWorkerStreamOwner)),
+ mCache(std::move(aCache)),
+ mOptimizedEncoding(aOptimizedEncoding),
+ mZStreamInitialized(false),
+ mConsumer(aConsumer),
+ mConsumerAborted(false) {
+ MOZ_DIAGNOSTIC_ASSERT(mWorkerStreamOwner);
+ MOZ_DIAGNOSTIC_ASSERT(mConsumer);
+ }
+
+ ~JSStreamConsumer() {
+ if (mZStreamInitialized) {
+ inflateEnd(&mZStream);
+ }
+
+ // Both WindowStreamOwner and WorkerStreamOwner need to be destroyed on
+ // their global's event target thread.
+
+ if (mWindowStreamOwner) {
+ MOZ_DIAGNOSTIC_ASSERT(!mWorkerStreamOwner);
+ NS_ReleaseOnMainThread("JSStreamConsumer::mWindowStreamOwner",
+ mWindowStreamOwner.forget(),
+ /* aAlwaysProxy = */ true);
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(mWorkerStreamOwner);
+ WorkerStreamOwner::ProxyRelease(mWorkerStreamOwner.forget());
+ }
+
+ // Bug 1733674: these annotations currently do nothing, because they are
+ // member variables and the annotation mechanism only applies to locals. But
+ // the analysis could be extended so that these could replace the big-hammer
+ // ~JSStreamConsumer annotation and thus the analysis could check that
+ // nothing is added that might GC for a different reason.
+ JS_HAZ_VALUE_IS_GC_SAFE(mWindowStreamOwner);
+ JS_HAZ_VALUE_IS_GC_SAFE(mWorkerStreamOwner);
+ }
+
+ 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);
+
+ if (self->mOptimizedEncoding) {
+ if (!self->mZStreamInitialized) {
+ // mOptimizedEncodingBytes is used as temporary storage until we have
+ // the full prefix.
+ MOZ_ASSERT(self->mOptimizedEncodingBytes.length() < PrefixBytes);
+ uint32_t remain = PrefixBytes - self->mOptimizedEncodingBytes.length();
+ uint32_t consume = std::min(remain, aCount);
+
+ if (!self->mOptimizedEncodingBytes.append(aFromSegment, consume)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (consume == remain) {
+ // Initialize zlib once all prefix bytes are loaded.
+ LengthPrefixType length;
+ memcpy(&length, self->mOptimizedEncodingBytes.begin(), PrefixBytes);
+
+ if (!self->mOptimizedEncodingBytes.resizeUninitialized(length)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ memset(&self->mZStream, 0, sizeof(self->mZStream));
+ self->mZStream.avail_out = length;
+ self->mZStream.next_out = self->mOptimizedEncodingBytes.begin();
+
+ if (inflateInit(&self->mZStream) != Z_OK) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ self->mZStreamInitialized = true;
+ }
+
+ *aWriteCount = consume;
+ return NS_OK;
+ }
+
+ // Zlib is initialized, overwrite the prefix with the inflated data.
+
+ MOZ_DIAGNOSTIC_ASSERT(aCount > 0);
+ self->mZStream.avail_in = aCount;
+ self->mZStream.next_in = (uint8_t*)aFromSegment;
+
+ int ret = inflate(&self->mZStream, Z_NO_FLUSH);
+
+ MOZ_DIAGNOSTIC_ASSERT(ret == Z_OK || ret == Z_STREAM_END,
+ "corrupt optimized wasm cache file: data");
+ MOZ_DIAGNOSTIC_ASSERT(self->mZStream.avail_in == 0,
+ "corrupt optimized wasm cache file: input");
+ MOZ_DIAGNOSTIC_ASSERT_IF(ret == Z_STREAM_END,
+ self->mZStream.avail_out == 0);
+ // Gracefully handle corruption in release.
+ bool ok =
+ (ret == Z_OK || ret == Z_STREAM_END) && self->mZStream.avail_in == 0;
+ if (!ok) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ // 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, nsIGlobalObject* aGlobal,
+ WorkerPrivate* aMaybeWorker, JS::StreamConsumer* aConsumer,
+ nsMainThreadPtrHandle<nsICacheInfoChannel>&& aCache,
+ bool aOptimizedEncoding) {
+ 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,
+ aGlobal->EventTargetFor(TaskCategory::Other));
+ if (!owner) {
+ return false;
+ }
+
+ consumer = new JSStreamConsumer(std::move(owner), aGlobal, aConsumer,
+ std::move(aCache), aOptimizedEncoding);
+ } else {
+ RefPtr<WindowStreamOwner> owner =
+ WindowStreamOwner::Create(asyncStream, aGlobal);
+ if (!owner) {
+ return false;
+ }
+
+ consumer = new JSStreamConsumer(owner.forget(), aGlobal, aConsumer,
+ std::move(aCache), aOptimizedEncoding);
+ }
+
+ // 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) {
+ if (mOptimizedEncoding) {
+ // Gracefully handle corruption of compressed data stream in release.
+ // From on investigations in bug 1738987, the incomplete data cases
+ // mostly happen during shutdown. Some corruptions in the cache entry
+ // can still happen and will be handled in the WriteSegment above.
+ bool ok = mZStreamInitialized && mZStream.avail_out == 0;
+ if (!ok) {
+ mConsumer->streamError(size_t(NS_ERROR_UNEXPECTED));
+ return NS_OK;
+ }
+
+ mConsumer->consumeOptimizedEncoding(mOptimizedEncodingBytes.begin(),
+ mOptimizedEncodingBytes.length());
+ } else {
+ // If there is cache entry associated with this stream, then listen for
+ // an optimized encoding so we can store it in the alt data. By JS API
+ // contract, the compilation process will hold a refcount to 'this'
+ // until it's done, optionally calling storeOptimizedEncoding().
+ mConsumer->streamEnd(mCache ? this : nullptr);
+ }
+ 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;
+ }
+
+ // JS::OptimizedEncodingListener
+
+ void storeOptimizedEncoding(const uint8_t* aSrcBytes,
+ size_t aSrcLength) override {
+ MOZ_ASSERT(mCache, "we only listen if there's a cache entry");
+
+ z_stream zstream;
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.avail_in = aSrcLength;
+ zstream.next_in = (uint8_t*)aSrcBytes;
+
+ // The wins from increasing compression levels are tiny, while the time
+ // to compress increases drastically. For example, for a 148mb alt-data
+ // produced by a 40mb .wasm file, the level 2 takes 2.5s to get a 3.7x size
+ // reduction while level 9 takes 22.5s to get a 4x size reduction. Read-time
+ // wins from smaller compressed cache files are not found to be
+ // significant, thus the fastest compression level is used. (On test
+ // workloads, level 2 actually was faster *and* smaller than level 1.)
+ const int COMPRESSION = 2;
+ if (deflateInit(&zstream, COMPRESSION) != Z_OK) {
+ return;
+ }
+ auto autoDestroy = MakeScopeExit([&]() { deflateEnd(&zstream); });
+
+ Vector<uint8_t> dstBytes;
+ if (!dstBytes.resizeUninitialized(PrefixBytes +
+ deflateBound(&zstream, aSrcLength))) {
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(LengthPrefixType(aSrcLength) == aSrcLength);
+ LengthPrefixType srcLength = aSrcLength;
+ memcpy(dstBytes.begin(), &srcLength, PrefixBytes);
+
+ uint8_t* compressBegin = dstBytes.begin() + PrefixBytes;
+ zstream.next_out = compressBegin;
+ zstream.avail_out = dstBytes.length() - PrefixBytes;
+
+ int ret = deflate(&zstream, Z_FINISH);
+ if (ret == Z_MEM_ERROR) {
+ return;
+ }
+ MOZ_RELEASE_ASSERT(ret == Z_STREAM_END);
+
+ dstBytes.shrinkTo(zstream.next_out - dstBytes.begin());
+
+ NS_DispatchToMainThread(new StoreOptimizedEncodingRunnable(
+ std::move(mCache), std::move(dstBytes)));
+ }
+};
+
+NS_IMPL_ISUPPORTS(JSStreamConsumer, nsIInputStreamCallback)
+
+// static
+const nsCString FetchUtil::WasmAltDataType;
+
+// static
+void FetchUtil::InitWasmAltDataType() {
+ nsCString& type = const_cast<nsCString&>(WasmAltDataType);
+ MOZ_ASSERT(type.IsEmpty());
+
+ RunOnShutdown([]() {
+ // Avoid nsStringBuffer leak tests failures.
+ const_cast<nsCString&>(WasmAltDataType).Truncate();
+ });
+
+ type.Append(nsLiteralCString("wasm-"));
+
+ JS::BuildIdCharVector buildId;
+ if (!JS::GetOptimizedEncodingBuildId(&buildId)) {
+ MOZ_CRASH("build id oom");
+ }
+
+ type.Append(buildId.begin(), buildId.length());
+}
+
+static bool ThrowException(JSContext* aCx, unsigned errorNumber) {
+ JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, errorNumber);
+ return false;
+}
+
+// static
+bool FetchUtil::StreamResponseToJS(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MimeType aMimeType,
+ JS::StreamConsumer* aConsumer,
+ WorkerPrivate* aMaybeWorker) {
+ MOZ_ASSERT(!WasmAltDataType.IsEmpty());
+ 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;
+ nsAutoCString mixedCaseMimeType; // unused
+ response->GetMimeType(mimeType, mixedCaseMimeType);
+
+ 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);
+ }
+
+ if (response->BodyUsed()) {
+ return ThrowException(aCx, JSMSG_WASM_RESPONSE_ALREADY_CONSUMED);
+ }
+
+ switch (aMimeType) {
+ case JS::MimeType::Wasm:
+ nsAutoString url;
+ response->GetUrl(url);
+
+ IgnoredErrorResult result;
+ 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;
+ }
+
+ SafeRefPtr<InternalResponse> ir = response->GetInternalResponse();
+ if (NS_WARN_IF(!ir)) {
+ return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+
+ nsMainThreadPtrHandle<nsICacheInfoChannel> cache;
+ bool optimizedEncoding = false;
+ if (ir->HasCacheInfoChannel()) {
+ cache = ir->TakeCacheInfoChannel();
+
+ nsAutoCString altDataType;
+ if (NS_SUCCEEDED(cache->GetAlternativeDataType(altDataType)) &&
+ WasmAltDataType.Equals(altDataType)) {
+ optimizedEncoding = true;
+ rv = cache->GetAlternativeDataInputStream(getter_AddRefs(stream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
+ }
+ if (ir->HasBeenCloned()) {
+ // If `Response` is cloned, clone alternative data stream instance.
+ // The cache entry does not clone automatically, and multiple
+ // JSStreamConsumer instances will collide during read if not cloned.
+ nsCOMPtr<nsICloneableInputStream> original = do_QueryInterface(stream);
+ if (NS_WARN_IF(!original)) {
+ return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
+ }
+ rv = original->Clone(getter_AddRefs(stream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
+ }
+ }
+ }
+ }
+
+ if (!optimizedEncoding) {
+ ir->GetUnfilteredBody(getter_AddRefs(stream));
+ if (!stream) {
+ aConsumer->streamEnd();
+ return true;
+ }
+ }
+
+ MOZ_ASSERT(stream);
+
+ 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(stream, global, aMaybeWorker, aConsumer,
+ std::move(cache), optimizedEncoding)) {
+ 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..c0c9157a43
--- /dev/null
+++ b/dom/fetch/FetchUtil.h
@@ -0,0 +1,82 @@
+/* -*- 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"
+
+class nsIPrincipal;
+class nsIHttpChannel;
+
+namespace mozilla::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);
+
+ /**
+ * The WebAssembly alt data type includes build-id, cpu-id and other relevant
+ * state that is necessary to ensure the validity of caching machine code and
+ * metadata in alt data. InitWasmAltDataType() must be called during startup
+ * before the first fetch(), ensuring that !WasmAltDataType.IsEmpty().
+ */
+ static const nsCString WasmAltDataType;
+ static void InitWasmAltDataType();
+
+ /**
+ * 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::Handle<JSObject*> 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 mozilla::dom
+#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..ea3b24405d
--- /dev/null
+++ b/dom/fetch/Headers.h
@@ -0,0 +1,132 @@
+/* -*- 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_WRAPPERCACHE_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 GetSetCookie(nsTArray<nsCString>& aValues) const {
+ mInternalHeaders->GetSetCookie(aValues);
+ }
+
+ 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..af24432b62
--- /dev/null
+++ b/dom/fetch/InternalHeaders.cpp
@@ -0,0 +1,654 @@
+/* -*- 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 "FetchUtil.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) {
+ if (IsForbiddenRequestHeader(aLowerName, aNormalizedValue)) {
+ 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
+ nsAutoCString value;
+ GetInternal(lowerName, value, aRv);
+ if (IsForbiddenRequestHeader(lowerName, value)) {
+ 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::GetSetCookie(nsTArray<nsCString>& aValues) const {
+ for (uint32_t i = 0; i < mList.Length(); ++i) {
+ if (mList[i].mName.EqualsIgnoreCase("Set-Cookie")) {
+ aValues.AppendElement(mList[i].mValue);
+ }
+ }
+}
+
+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 nsACString& aValue) const {
+ return mGuard == HeadersGuardEnum::Request &&
+ nsContentUtils::IsForbiddenRequestHeader(aName, aValue);
+}
+
+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("content-length") ||
+ 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;
+
+ // We combine every header but Set-Cookie.
+ if (!entry.mName.EqualsIgnoreCase("Set-Cookie")) {
+ 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..258725e32e
--- /dev/null
+++ b/dom/fetch/InternalHeaders.h
@@ -0,0 +1,180 @@
+/* -*- 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 GetSetCookie(nsTArray<nsCString>& aValues) 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 aCredentialsMode = 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 nsACString& aValue) 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, aValue) ||
+ 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..41797d3e82
--- /dev/null
+++ b/dom/fetch/InternalRequest.cpp
@@ -0,0 +1,478 @@
+/* -*- 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;
+ copy->mSkipWasmCaching = mSkipWasmCaching;
+ copy->mEmbedderPolicy = mEmbedderPolicy;
+ 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),
+ mSkipWasmCaching(aOther.mSkipWasmCaching),
+ mSynchronous(aOther.mSynchronous),
+ mUnsafeRequest(aOther.mUnsafeRequest),
+ mUseURLCredentials(aOther.mUseURLCredentials),
+ mContentPolicyTypeOverridden(aOther.mContentPolicyTypeOverridden),
+ mEmbedderPolicy(aOther.mEmbedderPolicy),
+ mInterceptionContentPolicyType(aOther.mInterceptionContentPolicyType),
+ mInterceptionRedirectChain(aOther.mInterceptionRedirectChain),
+ mInterceptionFromThirdParty(aOther.mInterceptionFromThirdParty) {
+ // NOTE: does not copy body stream... use the fallible Clone() for that
+
+ if (aOther.GetInterceptionTriggeringPrincipalInfo()) {
+ mInterceptionTriggeringPrincipalInfo =
+ MakeUnique<mozilla::ipc::PrincipalInfo>(
+ *(aOther.GetInterceptionTriggeringPrincipalInfo().get()));
+ }
+}
+
+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()),
+ mEnvironmentReferrerPolicy(aIPCRequest.environmentReferrerPolicy()),
+ mMode(aIPCRequest.requestMode()),
+ mCredentialsMode(aIPCRequest.requestCredentials()),
+ mCacheMode(aIPCRequest.cacheMode()),
+ mRedirectMode(aIPCRequest.requestRedirect()),
+ mIntegrity(aIPCRequest.integrity()),
+ mFragment(aIPCRequest.fragment()),
+ mEmbedderPolicy(aIPCRequest.embedderPolicy()),
+ mInterceptionContentPolicyType(static_cast<nsContentPolicyType>(
+ aIPCRequest.interceptionContentPolicyType())),
+ mInterceptionRedirectChain(aIPCRequest.interceptionRedirectChain()),
+ mInterceptionFromThirdParty(aIPCRequest.interceptionFromThirdParty()) {
+ if (aIPCRequest.principalInfo()) {
+ mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(
+ aIPCRequest.principalInfo().ref());
+ }
+
+ if (aIPCRequest.interceptionTriggeringPrincipalInfo()) {
+ mInterceptionTriggeringPrincipalInfo =
+ MakeUnique<mozilla::ipc::PrincipalInfo>(
+ aIPCRequest.interceptionTriggeringPrincipalInfo().ref());
+ }
+
+ const Maybe<BodyStreamVariant>& body = aIPCRequest.body();
+
+ if (body) {
+ if (body->type() == BodyStreamVariant::TParentToChildStream) {
+ mBodyStream = body->get_ParentToChildStream().get_RemoteLazyInputStream();
+ }
+ if (body->type() == BodyStreamVariant::TChildToParentStream) {
+ mBodyStream =
+ DeserializeIPCStream(body->get_ChildToParentStream().stream());
+ }
+ }
+}
+
+void InternalRequest::ToIPCInternalRequest(
+ IPCInternalRequest* aIPCRequest, mozilla::ipc::PBackgroundChild* aManager) {
+ aIPCRequest->method() = mMethod;
+ for (const auto& url : mURLList) {
+ aIPCRequest->urlList().AppendElement(url);
+ }
+ mHeaders->ToIPC(aIPCRequest->headers(), aIPCRequest->headersGuard());
+ aIPCRequest->bodySize() = mBodyLength;
+ aIPCRequest->preferredAlternativeDataType() = mPreferredAlternativeDataType;
+ aIPCRequest->contentPolicyType() = mContentPolicyType;
+ aIPCRequest->referrer() = mReferrer;
+ aIPCRequest->referrerPolicy() = mReferrerPolicy;
+ aIPCRequest->environmentReferrerPolicy() = mEnvironmentReferrerPolicy;
+ aIPCRequest->requestMode() = mMode;
+ aIPCRequest->requestCredentials() = mCredentialsMode;
+ aIPCRequest->cacheMode() = mCacheMode;
+ aIPCRequest->requestRedirect() = mRedirectMode;
+ aIPCRequest->integrity() = mIntegrity;
+ aIPCRequest->fragment() = mFragment;
+ aIPCRequest->embedderPolicy() = mEmbedderPolicy;
+
+ if (mPrincipalInfo) {
+ aIPCRequest->principalInfo() = Some(*mPrincipalInfo);
+ }
+
+ if (mBodyStream) {
+ nsCOMPtr<nsIInputStream> body = mBodyStream;
+ aIPCRequest->body().emplace(ChildToParentStream());
+ DebugOnly<bool> ok = mozilla::ipc::SerializeIPCStream(
+ body.forget(), aIPCRequest->body()->get_ChildToParentStream().stream(),
+ /* aAllowLazy */ false);
+ MOZ_ASSERT(ok);
+ }
+}
+
+InternalRequest::~InternalRequest() = default;
+
+void InternalRequest::SetContentPolicyType(
+ nsContentPolicyType aContentPolicyType) {
+ mContentPolicyType = aContentPolicyType;
+}
+
+void InternalRequest::OverrideContentPolicyType(
+ nsContentPolicyType aContentPolicyType) {
+ SetContentPolicyType(aContentPolicyType);
+ mContentPolicyTypeOverridden = true;
+}
+
+void InternalRequest::SetInterceptionContentPolicyType(
+ nsContentPolicyType aContentPolicyType) {
+ mInterceptionContentPolicyType = aContentPolicyType;
+}
+
+/* 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:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE:
+ 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:
+ case nsIContentPolicy::TYPE_UA_FONT:
+ 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_PROXIED_WEBRTC_MEDIA:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_WEB_IDENTITY:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_WEB_TRANSPORT:
+ return RequestDestination::_empty;
+ case nsIContentPolicy::TYPE_INVALID:
+ case nsIContentPolicy::TYPE_END:
+ 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);
+}
+
+void InternalRequest::SetInterceptionTriggeringPrincipalInfo(
+ UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo) {
+ mInterceptionTriggeringPrincipalInfo = std::move(aPrincipalInfo);
+}
+} // namespace mozilla::dom
diff --git a/dom/fetch/InternalRequest.h b/dom/fetch/InternalRequest.h
new file mode 100644
index 0000000000..3dfbb9284d
--- /dev/null
+++ b/dom/fetch/InternalRequest.h
@@ -0,0 +1,489 @@
+/* -*- 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"
+#include "mozilla/net/NeckoChannelParams.h"
+#ifdef DEBUG
+# include "nsIURLParser.h"
+# include "nsNetCID.h"
+# include "nsServiceManagerUtils.h"
+#endif
+
+using mozilla::net::RedirectHistoryEntryInfo;
+
+namespace mozilla {
+
+namespace ipc {
+class PBackgroundChild;
+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, TYPE_INTERNAL_WORKER_STATIC_MODULE
+ * "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);
+
+ void ToIPCInternalRequest(IPCInternalRequest* aIPCRequest,
+ mozilla::ipc::PBackgroundChild* aManager);
+
+ 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 SkipWasmCaching() const { return mSkipWasmCaching; }
+
+ void SetSkipWasmCaching() { mSkipWasmCaching = 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;
+ }
+
+ void SetInterceptionTriggeringPrincipalInfo(
+ UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo);
+
+ const UniquePtr<mozilla::ipc::PrincipalInfo>&
+ GetInterceptionTriggeringPrincipalInfo() const {
+ return mInterceptionTriggeringPrincipalInfo;
+ }
+
+ nsContentPolicyType InterceptionContentPolicyType() const {
+ return mInterceptionContentPolicyType;
+ }
+ void SetInterceptionContentPolicyType(nsContentPolicyType aContentPolicyType);
+
+ const nsTArray<RedirectHistoryEntryInfo>& InterceptionRedirectChain() const {
+ return mInterceptionRedirectChain;
+ }
+
+ void SetInterceptionRedirectChain(
+ const nsTArray<RedirectHistoryEntryInfo>& aRedirectChain) {
+ mInterceptionRedirectChain = aRedirectChain;
+ }
+
+ const bool& InterceptionFromThirdParty() const {
+ return mInterceptionFromThirdParty;
+ }
+
+ void SetInterceptionFromThirdParty(bool aFromThirdParty) {
+ mInterceptionFromThirdParty = aFromThirdParty;
+ }
+
+ 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 mSkipWasmCaching = 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;
+
+ // Following members are specific for the FetchEvent.request or
+ // NavigationPreload request which is extracted from the
+ // InterceptedHttpChannel.
+ // Notice that these members would not be copied when calling
+ // InternalRequest::GetRequestConstructorCopy() since these information should
+ // not be propagated when copying the Request in ServiceWorker script.
+
+ // This is the trigging principalInfo of the InterceptedHttpChannel.
+ UniquePtr<mozilla::ipc::PrincipalInfo> mInterceptionTriggeringPrincipalInfo;
+
+ // This is the contentPolicyType of the InterceptedHttpChannel.
+ nsContentPolicyType mInterceptionContentPolicyType{
+ nsIContentPolicy::TYPE_INVALID};
+
+ // This is the redirect history of the InterceptedHttpChannel.
+ CopyableTArray<RedirectHistoryEntryInfo> mInterceptionRedirectChain;
+
+ // This indicates that the InterceptedHttpChannel is a third party channel.
+ bool mInterceptionFromThirdParty{false};
+};
+
+} // 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..b54e400941
--- /dev/null
+++ b/dom/fetch/InternalResponse.cpp
@@ -0,0 +1,458 @@
+/* -*- 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/FetchStreamUtils.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;
+
+} // 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),
+ mCloned(false) {}
+
+/* static */ SafeRefPtr<InternalResponse> InternalResponse::FromIPC(
+ const ParentToParentInternalResponse& aIPCResponse) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return FromIPCTemplate(aIPCResponse);
+}
+
+/* static */ SafeRefPtr<InternalResponse> InternalResponse::FromIPC(
+ const ParentToChildInternalResponse& aIPCResponse) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ return FromIPCTemplate(aIPCResponse);
+}
+
+template <typename T>
+/* static */ SafeRefPtr<InternalResponse> InternalResponse::FromIPCTemplate(
+ const T& aIPCResponse) {
+ if (aIPCResponse.metadata().type() == ResponseType::Error) {
+ return InternalResponse::NetworkError(aIPCResponse.metadata().errorCode());
+ }
+
+ SafeRefPtr<InternalResponse> response = MakeSafeRefPtr<InternalResponse>(
+ aIPCResponse.metadata().status(), aIPCResponse.metadata().statusText());
+
+ response->SetURLList(aIPCResponse.metadata().urlList());
+ response->mHeaders =
+ new InternalHeaders(aIPCResponse.metadata().headers(),
+ aIPCResponse.metadata().headersGuard());
+
+ if (aIPCResponse.body()) {
+ auto bodySize = aIPCResponse.bodySize();
+ auto body = ToInputStream(*aIPCResponse.body());
+ response->SetBody(body.get(), bodySize);
+ }
+
+ response->SetAlternativeDataType(
+ aIPCResponse.metadata().alternativeDataType());
+
+ if (aIPCResponse.alternativeBody()) {
+ auto alternativeBody = ToInputStream(*aIPCResponse.alternativeBody());
+ response->SetAlternativeBody(alternativeBody.get());
+ }
+
+ response->InitChannelInfo(aIPCResponse.metadata().securityInfo());
+
+ if (aIPCResponse.metadata().principalInfo()) {
+ response->SetPrincipalInfo(MakeUnique<mozilla::ipc::PrincipalInfo>(
+ aIPCResponse.metadata().principalInfo().ref()));
+ }
+
+ nsAutoCString bodyBlobURISpec(aIPCResponse.metadata().bodyBlobURISpec());
+ response->SetBodyBlobURISpec(bodyBlobURISpec);
+ nsAutoString bodyLocalPath(aIPCResponse.metadata().bodyLocalPath());
+ response->SetBodyLocalPath(bodyLocalPath);
+
+ response->mCredentialsMode = aIPCResponse.metadata().credentialsMode();
+
+ switch (aIPCResponse.metadata().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;
+
+InternalResponseMetadata InternalResponse::GetMetadata() {
+ nsTArray<HeadersEntry> headers;
+ HeadersGuardEnum headersGuard;
+ UnfilteredHeaders()->ToIPC(headers, headersGuard);
+
+ Maybe<mozilla::ipc::PrincipalInfo> principalInfo =
+ mPrincipalInfo ? Some(*mPrincipalInfo) : Nothing();
+
+ nsAutoCString bodyBlobURISpec(BodyBlobURISpec());
+ nsAutoString bodyLocalPath(BodyLocalPath());
+
+ // Note: all the arguments are copied rather than moved, which would be more
+ // efficient, because there's no move-friendly constructor generated.
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo(mChannelInfo.SecurityInfo());
+ return InternalResponseMetadata(
+ mType, GetUnfilteredURLList(), GetUnfilteredStatus(),
+ GetUnfilteredStatusText(), headersGuard, headers, mErrorCode,
+ GetAlternativeDataType(), securityInfo, principalInfo, bodyBlobURISpec,
+ bodyLocalPath, GetCredentialsMode());
+}
+
+void InternalResponse::ToChildToParentInternalResponse(
+ ChildToParentInternalResponse* aIPCResponse,
+ mozilla::ipc::PBackgroundChild* aManager) {
+ *aIPCResponse = ChildToParentInternalResponse(GetMetadata(), Nothing(),
+ UNKNOWN_BODY_SIZE, Nothing());
+
+ nsCOMPtr<nsIInputStream> body;
+ int64_t bodySize;
+ GetUnfilteredBody(getter_AddRefs(body), &bodySize);
+
+ if (body) {
+ aIPCResponse->body().emplace(ChildToParentStream());
+ aIPCResponse->bodySize() = bodySize;
+
+ DebugOnly<bool> ok = mozilla::ipc::SerializeIPCStream(
+ body.forget(), aIPCResponse->body()->stream(), /* aAllowLazy */ false);
+ MOZ_ASSERT(ok);
+ }
+
+ nsCOMPtr<nsIInputStream> alternativeBody = TakeAlternativeBody();
+ if (alternativeBody) {
+ aIPCResponse->alternativeBody().emplace(ChildToParentStream());
+
+ DebugOnly<bool> ok = mozilla::ipc::SerializeIPCStream(
+ alternativeBody.forget(), aIPCResponse->alternativeBody()->stream(),
+ /* aAllowLazy */ false);
+ MOZ_ASSERT(ok);
+ }
+}
+
+ParentToParentInternalResponse
+InternalResponse::ToParentToParentInternalResponse() {
+ ParentToParentInternalResponse result(GetMetadata(), Nothing(),
+ UNKNOWN_BODY_SIZE, Nothing());
+
+ nsCOMPtr<nsIInputStream> body;
+ int64_t bodySize;
+ GetUnfilteredBody(getter_AddRefs(body), &bodySize);
+
+ if (body) {
+ result.body() = Some(ToParentToParentStream(WrapNotNull(body), bodySize));
+ result.bodySize() = bodySize;
+ }
+
+ nsCOMPtr<nsIInputStream> alternativeBody = TakeAlternativeBody();
+ if (alternativeBody) {
+ result.alternativeBody() = Some(ToParentToParentStream(
+ WrapNotNull(alternativeBody), UNKNOWN_BODY_SIZE));
+ }
+
+ return result;
+}
+
+ParentToChildInternalResponse InternalResponse::ToParentToChildInternalResponse(
+ NotNull<mozilla::ipc::PBackgroundParent*> aBackgroundParent) {
+ ParentToChildInternalResponse result(GetMetadata(), Nothing(),
+ UNKNOWN_BODY_SIZE, Nothing());
+
+ nsCOMPtr<nsIInputStream> body;
+ int64_t bodySize;
+ GetUnfilteredBody(getter_AddRefs(body), &bodySize);
+
+ if (body) {
+ ParentToChildStream bodyStream = ToParentToChildStream(
+ WrapNotNull(body), bodySize, aBackgroundParent, mSerializeAsLazy);
+ // The body stream can fail to serialize as an IPCStream. In the case, the
+ // IPCStream's type would be T__None. Don't set up IPCInternalResponse's
+ // body with the failed IPCStream.
+ if (mSerializeAsLazy || bodyStream.get_IPCStream().stream().type() !=
+ mozilla::ipc::InputStreamParams::T__None) {
+ result.body() = Some(bodyStream);
+ result.bodySize() = bodySize;
+ }
+ }
+
+ nsCOMPtr<nsIInputStream> alternativeBody = TakeAlternativeBody();
+ if (alternativeBody) {
+ ParentToChildStream alterBodyStream =
+ ToParentToChildStream(WrapNotNull(alternativeBody), UNKNOWN_BODY_SIZE,
+ aBackgroundParent, mSerializeAsLazy);
+ // The body stream can fail to serialize as an IPCStream. In the case, the
+ // IPCStream's type would be T__None. Don't set up IPCInternalResponse's
+ // body with the failed IPCStream.
+ if (mSerializeAsLazy || alterBodyStream.get_IPCStream().stream().type() !=
+ mozilla::ipc::InputStreamParams::T__None) {
+ result.alternativeBody() = Some(alterBodyStream);
+ }
+ }
+
+ return result;
+}
+
+SafeRefPtr<InternalResponse> InternalResponse::Clone(CloneType aCloneType) {
+ SafeRefPtr<InternalResponse> clone = CreateIncompleteCopy();
+ clone->mCloned = (mCloned = true);
+
+ 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;
+ clone->mCredentialsMode = mCredentialsMode;
+
+ if (mWrappedResponse) {
+ clone->mWrappedResponse = mWrappedResponse->Clone(aCloneType);
+ MOZ_ASSERT(!mBody);
+ return clone;
+ }
+
+ if (!mBody || aCloneType == eDontCloneInputStream) {
+ return clone;
+ }
+
+ 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;
+}
+
+SafeRefPtr<InternalResponse> InternalResponse::BasicResponse() {
+ MOZ_ASSERT(!mWrappedResponse,
+ "Can't BasicResponse a already wrapped response");
+ SafeRefPtr<InternalResponse> basic = CreateIncompleteCopy();
+ basic->mType = ResponseType::Basic;
+ basic->mHeaders = InternalHeaders::BasicHeaders(Headers());
+ basic->mWrappedResponse = SafeRefPtrFromThis();
+ return basic;
+}
+
+SafeRefPtr<InternalResponse> InternalResponse::CORSResponse() {
+ MOZ_ASSERT(!mWrappedResponse,
+ "Can't CORSResponse a already wrapped response");
+ SafeRefPtr<InternalResponse> cors = CreateIncompleteCopy();
+ cors->mType = ResponseType::Cors;
+ cors->mHeaders = InternalHeaders::CORSHeaders(Headers(), mCredentialsMode);
+ cors->mWrappedResponse = SafeRefPtrFromThis();
+ return cors;
+}
+
+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;
+ }
+}
+
+SafeRefPtr<InternalResponse> InternalResponse::Unfiltered() {
+ SafeRefPtr<InternalResponse> ref = mWrappedResponse.clonePtr();
+ if (!ref) {
+ ref = SafeRefPtrFromThis();
+ }
+ return ref;
+}
+
+SafeRefPtr<InternalResponse> InternalResponse::OpaqueResponse() {
+ MOZ_ASSERT(!mWrappedResponse,
+ "Can't OpaqueResponse a already wrapped response");
+ SafeRefPtr<InternalResponse> response =
+ MakeSafeRefPtr<InternalResponse>(0, ""_ns);
+ response->mType = ResponseType::Opaque;
+ response->mChannelInfo = mChannelInfo;
+ if (mPrincipalInfo) {
+ response->mPrincipalInfo =
+ MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);
+ }
+ response->mWrappedResponse = SafeRefPtrFromThis();
+ return response;
+}
+
+SafeRefPtr<InternalResponse> InternalResponse::OpaqueRedirectResponse() {
+ MOZ_ASSERT(!mWrappedResponse,
+ "Can't OpaqueRedirectResponse a already wrapped response");
+ MOZ_ASSERT(!mURLList.IsEmpty(),
+ "URLList should not be emtpy for internalResponse");
+ SafeRefPtr<InternalResponse> response = OpaqueResponse();
+ response->mType = ResponseType::Opaqueredirect;
+ response->mURLList = mURLList.Clone();
+ return response;
+}
+
+SafeRefPtr<InternalResponse> InternalResponse::CreateIncompleteCopy() {
+ SafeRefPtr<InternalResponse> copy =
+ MakeSafeRefPtr<InternalResponse>(mStatus, mStatusText);
+ copy->mType = mType;
+ copy->mURLList = mURLList.Clone();
+ copy->mChannelInfo = mChannelInfo;
+ if (mPrincipalInfo) {
+ copy->mPrincipalInfo =
+ MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);
+ }
+ return copy;
+}
+
+ParentToChildInternalResponse ToParentToChild(
+ const ParentToParentInternalResponse& aResponse,
+ NotNull<mozilla::ipc::PBackgroundParent*> aBackgroundParent) {
+ ParentToChildInternalResponse result(aResponse.metadata(), Nothing(),
+ aResponse.bodySize(), Nothing());
+
+ if (aResponse.body().isSome()) {
+ result.body() = Some(ToParentToChildStream(
+ aResponse.body().ref(), aResponse.bodySize(), aBackgroundParent));
+ }
+ if (aResponse.alternativeBody().isSome()) {
+ result.alternativeBody() = Some(ToParentToChildStream(
+ aResponse.alternativeBody().ref(), InternalResponse::UNKNOWN_BODY_SIZE,
+ aBackgroundParent));
+ }
+
+ return result;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/InternalResponse.h b/dom/fetch/InternalResponse.h
new file mode 100644
index 0000000000..e8a492a314
--- /dev/null
+++ b/dom/fetch/InternalResponse.h
@@ -0,0 +1,419 @@
+/* -*- 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 "mozilla/dom/FetchTypes.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/dom/SafeRefPtr.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace ipc {
+class PBackgroundChild;
+class PBackgroundParent;
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class ChildToParentInternalResponse;
+class InternalHeaders;
+class ParentToChildInternalResponse;
+class ParentToParentInternalResponse;
+
+class InternalResponse final : public AtomicSafeRefCounted<InternalResponse> {
+ friend class FetchDriver;
+
+ public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(InternalResponse)
+
+ InternalResponse(
+ uint16_t aStatus, const nsACString& aStatusText,
+ RequestCredentials aCredentialsMode = RequestCredentials::Omit);
+
+ static SafeRefPtr<InternalResponse> FromIPC(
+ const ParentToChildInternalResponse& aIPCResponse);
+
+ static SafeRefPtr<InternalResponse> FromIPC(
+ const ParentToParentInternalResponse& aIPCResponse);
+
+ void ToChildToParentInternalResponse(
+ ChildToParentInternalResponse* aIPCResponse,
+ mozilla::ipc::PBackgroundChild* aManager);
+
+ ParentToParentInternalResponse ToParentToParentInternalResponse();
+
+ ParentToChildInternalResponse ToParentToChildInternalResponse(
+ NotNull<mozilla::ipc::PBackgroundParent*> aBackgroundParent);
+
+ enum CloneType {
+ eCloneInputStream,
+ eDontCloneInputStream,
+ };
+
+ SafeRefPtr<InternalResponse> Clone(CloneType aCloneType);
+
+ static SafeRefPtr<InternalResponse> NetworkError(nsresult aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aRv));
+ SafeRefPtr<InternalResponse> response =
+ MakeSafeRefPtr<InternalResponse>(0, ""_ns);
+ ErrorResult result;
+ response->Headers()->SetGuard(HeadersGuardEnum::Immutable, result);
+ MOZ_ASSERT(!result.Failed());
+ response->mType = ResponseType::Error;
+ response->mErrorCode = aRv;
+ return response;
+ }
+
+ SafeRefPtr<InternalResponse> OpaqueResponse();
+
+ SafeRefPtr<InternalResponse> OpaqueRedirectResponse();
+
+ SafeRefPtr<InternalResponse> BasicResponse();
+
+ SafeRefPtr<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;
+ }
+
+ bool HasBeenCloned() const { return mCloned; }
+
+ void SetSerializeAsLazy(bool aAllow) { mSerializeAsLazy = aAllow; }
+ bool CanSerializeAsLazy() const { return mSerializeAsLazy; }
+
+ void InitChannelInfo(nsIChannel* aChannel) {
+ mChannelInfo.InitFromChannel(aChannel);
+ }
+
+ void InitChannelInfo(nsITransportSecurityInfo* aSecurityInfo) {
+ mChannelInfo.InitFromTransportSecurityInfo(aSecurityInfo);
+ }
+
+ 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;
+
+ SafeRefPtr<InternalResponse> Unfiltered();
+
+ InternalResponseMetadata GetMetadata();
+
+ RequestCredentials GetCredentialsMode() const {
+ if (mWrappedResponse) {
+ return mWrappedResponse->GetCredentialsMode();
+ }
+ return mCredentialsMode;
+ }
+
+ ~InternalResponse();
+
+ private:
+ 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.
+ SafeRefPtr<InternalResponse> CreateIncompleteCopy();
+
+ template <typename T>
+ static SafeRefPtr<InternalResponse> FromIPCTemplate(const T& aIPCResponse);
+
+ 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;
+ bool mCloned;
+ // boolean to indicate the body/alternativeBody will be serialized as a
+ // RemoteLazyInputStream.
+ bool mSerializeAsLazy{true};
+
+ public:
+ static constexpr int64_t UNKNOWN_BODY_SIZE = -1;
+ static constexpr 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.
+ SafeRefPtr<InternalResponse> mWrappedResponse;
+};
+
+ParentToChildInternalResponse ToParentToChild(
+ const ParentToParentInternalResponse& aResponse,
+ NotNull<mozilla::ipc::PBackgroundParent*> aBackgroundParent);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_InternalResponse_h
diff --git a/dom/fetch/PFetch.ipdl b/dom/fetch/PFetch.ipdl
new file mode 100644
index 0000000000..904ef0738d
--- /dev/null
+++ b/dom/fetch/PFetch.ipdl
@@ -0,0 +1,56 @@
+/* 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 protocol PBackground;
+
+include ClientIPCTypes;
+include FetchTypes;
+include IPCServiceWorkerDescriptor;
+include NeckoChannelParams;
+include PBackgroundSharedTypes;
+include PerformanceTimingTypes;
+
+namespace mozilla {
+namespace dom {
+
+struct FetchOpArgs{
+ IPCInternalRequest request;
+ PrincipalInfo principalInfo;
+ nsCString workerScript;
+ IPCClientInfo clientInfo;
+ IPCServiceWorkerDescriptor? controller;
+ CookieJarSettingsArgs? cookieJarSettings;
+ bool needOnDataAvailable;
+ bool hasCSPEventListener;
+ uint64_t associatedBrowsingContextID;
+};
+
+protocol PFetch {
+ manager PBackground;
+
+ parent:
+ async FetchOp(FetchOpArgs aArgs);
+
+ async AbortFetchOp();
+
+ child:
+ async OnResponseAvailableInternal(ParentToChildInternalResponse aResponse);
+
+ async OnResponseEnd(ResponseEndArgs aResponseEndArgs);
+
+ async OnDataAvailable();
+
+ async OnFlushConsoleReport(ConsoleReportCollected[] aReports);
+
+ async OnCSPViolationEvent(nsString aJSON);
+
+ async OnReportPerformanceTiming(ResponseTiming aTiming);
+
+ async OnNotifyNetworkMonitorAlternateStack(uint64_t aChannelID);
+
+ async __delete__(nsresult aResult);
+};
+
+}
+}
diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp
new file mode 100644
index 0000000000..4fcb19b6f7
--- /dev/null
+++ b/dom/fetch/Request.cpp
@@ -0,0 +1,658 @@
+/* -*- 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 "js/Value.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/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/Unused.h"
+
+#include "mozilla/dom/ReadableStreamDefaultReader.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ADDREF_INHERITED(Request, FetchBody<Request>)
+NS_IMPL_RELEASE_INHERITED(Request, FetchBody<Request>)
+
+// Can't use _INHERITED macro here because FetchBody<Request> is abstract.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_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)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader)
+ 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)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_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.
+ JS::Rooted<JS::Value> reason(RootingCx(), aSignal->RawReason());
+ mSignal = new AbortSignal(aOwner, aSignal->Aborted(), reason);
+ 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));
+ if (inputReq->BodyUsed()) {
+ 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) {
+ if (BodyUsed()) {
+ 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, JS::UndefinedHandleValue);
+ }
+
+ return mSignal;
+}
+
+AbortSignalImpl* Request::GetSignalImpl() const { return mSignal; }
+
+AbortSignalImpl* Request::GetSignalImplToConsumeBody() const {
+ // This is a hack, see Response::GetSignalImplToConsumeBody.
+ return nullptr;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fetch/Request.h b/dom/fetch/Request.h
new file mode 100644
index 0000000000..0086e076de
--- /dev/null
+++ b/dom/fetch/Request.h
@@ -0,0 +1,142 @@
+/* -*- 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::dom {
+
+class Headers;
+class InternalHeaders;
+class RequestOrUSVString;
+
+class Request final : public FetchBody<Request>, public nsWrapperCache {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_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;
+ AbortSignalImpl* GetSignalImplToConsumeBody() const final;
+
+ private:
+ ~Request();
+
+ SafeRefPtr<InternalRequest> mRequest;
+
+ // Lazily created.
+ RefPtr<Headers> mHeaders;
+ RefPtr<AbortSignal> mSignal;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_Request_h
diff --git a/dom/fetch/Response.cpp b/dom/fetch/Response.cpp
new file mode 100644
index 0000000000..fbdc5b0d20
--- /dev/null
+++ b/dom/fetch/Response.cpp
@@ -0,0 +1,477 @@
+/* -*- 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/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"
+
+#include "mozilla/dom/ReadableStreamDefaultReader.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>)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignalImpl)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Response, FetchBody<Response>)
+ 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_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,
+ SafeRefPtr<InternalResponse> aInternalResponse,
+ AbortSignalImpl* aSignalImpl)
+ : FetchBody<Response>(aGlobal),
+ mInternalResponse(std::move(aInternalResponse)),
+ mSignalImpl(aSignalImpl) {
+ MOZ_ASSERT(
+ mInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable ||
+ mInternalResponse->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<Response> r = new Response(
+ global, InternalResponse::NetworkError(NS_ERROR_FAILURE), 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::CreateAndInitializeAResponse(
+ const GlobalObject& aGlobal, const Nullable<fetch::ResponseBodyInit>& aBody,
+ const nsACString& aDefaultContentType, 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;
+ }
+
+ // Initialize a response, Step 1.
+ if (aInit.mStatus < 200 || aInit.mStatus > 599) {
+ aRv.ThrowRangeError("Invalid response status code.");
+ return nullptr;
+ }
+
+ // Initialize a response, Step 2: 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;
+ }
+
+ // Initialize a response, Step 3-4.
+ SafeRefPtr<InternalResponse> internalResponse =
+ MakeSafeRefPtr<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.clonePtr(), 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()) {
+ JSContext* cx = aGlobal.Context();
+ aRv.MightThrowJSException();
+
+ ReadableStream& readableStream = body.GetAsReadableStream();
+
+ if (readableStream.Locked() || readableStream.Disturbed()) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ r->SetReadableStreamBody(cx, &readableStream);
+
+ // If this is a DOM generated ReadableStream, we can extract the
+ // inputStream directly.
+ if (nsIInputStream* underlyingSource =
+ readableStream.MaybeGetInputStreamIfUnread()) {
+ bodyStream = underlyingSource;
+ } 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;
+ }
+
+ if (!aDefaultContentType.IsVoid()) {
+ contentTypeWithCharset = aDefaultContentType;
+ }
+
+ 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();
+}
+
+/* static */
+already_AddRefed<Response> Response::CreateFromJson(const GlobalObject& aGlobal,
+ JSContext* aCx,
+ JS::Handle<JS::Value> aData,
+ const ResponseInit& aInit,
+ ErrorResult& aRv) {
+ aRv.MightThrowJSException();
+ nsAutoString serializedValue;
+ if (!nsContentUtils::StringifyJSON(aCx, aData, serializedValue,
+ UndefinedIsVoidString)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+ if (serializedValue.IsVoid()) {
+ aRv.ThrowTypeError<MSG_JSON_INVALID_VALUE>();
+ return nullptr;
+ }
+ Nullable<fetch::ResponseBodyInit> body;
+ body.SetValue().SetAsUSVString().ShareOrDependUpon(serializedValue);
+ return CreateAndInitializeAResponse(aGlobal, body, "application/json"_ns,
+ aInit, aRv);
+}
+
+/*static*/
+already_AddRefed<Response> Response::Constructor(
+ const GlobalObject& aGlobal, const Nullable<fetch::ResponseBodyInit>& aBody,
+ const ResponseInit& aInit, ErrorResult& aRv) {
+ return CreateAndInitializeAResponse(aGlobal, aBody, VoidCString(), aInit,
+ aRv);
+}
+
+already_AddRefed<Response> Response::Clone(JSContext* aCx, ErrorResult& aRv) {
+ bool bodyUsed = BodyUsed();
+
+ if (!bodyUsed && mReadableStreamBody) {
+ bool locked = mReadableStreamBody->Locked();
+ bodyUsed = locked;
+ }
+
+ if (bodyUsed) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ RefPtr<FetchStreamReader> streamReader;
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ RefPtr<ReadableStream> body;
+ MaybeTeeReadableStreamBody(aCx, getter_AddRefs(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);
+
+ SafeRefPtr<InternalResponse> ir =
+ mInternalResponse->Clone(body ? InternalResponse::eDontCloneInputStream
+ : InternalResponse::eCloneInputStream);
+
+ RefPtr<Response> response =
+ new Response(mOwner, ir.clonePtr(), 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 (BodyUsed()) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ RefPtr<FetchStreamReader> streamReader;
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ RefPtr<ReadableStream> body;
+ MaybeTeeReadableStreamBody(aCx, getter_AddRefs(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);
+
+ SafeRefPtr<InternalResponse> clone =
+ mInternalResponse->Clone(body ? InternalResponse::eDontCloneInputStream
+ : InternalResponse::eCloneInputStream);
+
+ SafeRefPtr<InternalResponse> ir = clone->Unfiltered();
+ RefPtr<Response> ref = new Response(mOwner, ir.clonePtr(), 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(!BodyUsed());
+ mInternalResponse->SetBody(aBody, aBodySize);
+}
+
+SafeRefPtr<InternalResponse> Response::GetInternalResponse() const {
+ return mInternalResponse.clonePtr();
+}
+
+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..1aa0e56c31
--- /dev/null
+++ b/dom/fetch/Response.h
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_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,
+ SafeRefPtr<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> CreateFromJson(const GlobalObject&,
+ JSContext*,
+ JS::Handle<JS::Value>,
+ const ResponseInit&,
+ ErrorResult&);
+
+ 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);
+
+ SafeRefPtr<InternalResponse> GetInternalResponse() const;
+
+ AbortSignalImpl* GetSignalImpl() const override { return mSignalImpl; }
+ AbortSignalImpl* GetSignalImplToConsumeBody() const final {
+ // XXX: BodyConsumer is supposed to work in terms of ReadableStream and
+ // should be affected by: https://fetch.spec.whatwg.org/#abort-fetch
+ //
+ // Step 6: If response’s body is not null and is readable, then error
+ // response’s body with error.
+ //
+ // But since it's written before streams work, it's currently depending on
+ // abort signal to be aborted.
+ // Please fix this when DOM ReadableStream is ready. (Bug 1730584)
+ return mSignalImpl;
+ }
+
+ private:
+ static already_AddRefed<Response> CreateAndInitializeAResponse(
+ const GlobalObject& aGlobal,
+ const Nullable<fetch::ResponseBodyInit>& aBody,
+ const nsACString& aDefaultContentType, const ResponseInit& aInit,
+ ErrorResult& aRv);
+
+ ~Response();
+
+ SafeRefPtr<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..6d7aa31027
--- /dev/null
+++ b/dom/fetch/moz.build
@@ -0,0 +1,72 @@
+# -*- 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",
+ "FetchChild.h",
+ "FetchDriver.h",
+ "FetchIPCTypes.h",
+ "FetchObserver.h",
+ "FetchParent.h",
+ "FetchService.h",
+ "FetchStreamReader.h",
+ "FetchStreamUtils.h",
+ "FetchUtil.h",
+ "Headers.h",
+ "InternalHeaders.h",
+ "InternalRequest.h",
+ "InternalResponse.h",
+ "Request.h",
+ "Response.h",
+]
+
+UNIFIED_SOURCES += [
+ "BodyExtractor.cpp",
+ "ChannelInfo.cpp",
+ "Fetch.cpp",
+ "FetchChild.cpp",
+ "FetchDriver.cpp",
+ "FetchObserver.cpp",
+ "FetchParent.cpp",
+ "FetchService.cpp",
+ "FetchStreamReader.cpp",
+ "FetchStreamUtils.cpp",
+ "FetchUtil.cpp",
+ "Headers.cpp",
+ "InternalHeaders.cpp",
+ "InternalRequest.cpp",
+ "InternalResponse.cpp",
+ "Request.cpp",
+ "Response.cpp",
+]
+
+IPDL_SOURCES += [
+ "FetchTypes.ipdlh",
+ "PFetch.ipdl",
+]
+
+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/browser.ini b/dom/fetch/tests/browser.ini
new file mode 100644
index 0000000000..6fd8244288
--- /dev/null
+++ b/dom/fetch/tests/browser.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+[browser_blobFromFile.js]
+[browser_origin_trial_coep_credentialless_fetch_1.js]
+support-files =
+ open_credentialless_document.sjs
+ store_header.sjs
+[browser_origin_trial_coep_credentialless_fetch_2.js]
+support-files =
+ open_credentialless_document.sjs
+ store_header.sjs
+[browser_origin_trial_coep_credentialless_fetch_3.js]
+support-files =
+ open_credentialless_document.sjs
+ store_header.sjs
+[browser_origin_trial_coep_credentialless_worker.js]
+support-files =
+ open_credentialless_document.sjs
+ store_header.sjs
+ credentialless_worker.sjs
+[browser_origin_trial_coep_credentialless_cache.js]
+support-files =
+ open_credentialless_document.sjs
+ credentialless_resource.sjs
diff --git a/dom/fetch/tests/browser_blobFromFile.js b/dom/fetch/tests/browser_blobFromFile.js
new file mode 100644
index 0000000000..07a8c6669e
--- /dev/null
+++ b/dom/fetch/tests/browser_blobFromFile.js
@@ -0,0 +1,64 @@
+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
+ 0o666,
+ 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.getURLSpecFromActualFile(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(File.isInstance(blob), "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/browser_origin_trial_coep_credentialless_cache.js b/dom/fetch/tests/browser_origin_trial_coep_credentialless_cache.js
new file mode 100644
index 0000000000..c9b72a3393
--- /dev/null
+++ b/dom/fetch/tests/browser_origin_trial_coep_credentialless_cache.js
@@ -0,0 +1,139 @@
+const TOP_LEVEL_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "open_credentialless_document.sjs";
+
+const SAME_ORIGIN = "https://example.com";
+const CROSS_ORIGIN = "https://test1.example.com";
+
+const RESOURCE_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://test1.example.com"
+ ) + "credentialless_resource.sjs";
+
+async function store(storer, url, requestCredentialMode) {
+ await SpecialPowers.spawn(
+ storer.linkedBrowser,
+ [url, requestCredentialMode],
+ async function (url, requestCredentialMode) {
+ const cache = await content.caches.open("v1");
+ const fetchRequest = new content.Request(url, {
+ mode: "no-cors",
+ credentials: requestCredentialMode,
+ });
+
+ const fetchResponse = await content.fetch(fetchRequest);
+ content.wrappedJSObject.console.log(fetchResponse.headers);
+ await cache.put(fetchRequest, fetchResponse);
+ }
+ );
+}
+
+async function retrieve(retriever, resourceURL) {
+ return await SpecialPowers.spawn(
+ retriever.linkedBrowser,
+ [resourceURL],
+ async function (url) {
+ const cache = await content.caches.open("v1");
+ try {
+ await cache.match(url);
+ return "retrieved";
+ } catch (error) {
+ return "error";
+ }
+ }
+ );
+}
+
+async function testCache(
+ storer,
+ storeRequestCredentialMode,
+ resourceCOEP,
+ retriever,
+ expectation
+) {
+ const resourceURL = RESOURCE_URL + "?" + resourceCOEP;
+
+ await store(storer, resourceURL, storeRequestCredentialMode);
+ const result = await retrieve(retriever, resourceURL);
+
+ is(result, expectation);
+}
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.tabs.remote.coep.credentialless", false],
+ ["dom.origin-trials.enabled", true],
+ ["dom.origin-trials.test-key.enabled", true],
+ ],
+ });
+
+ const noneTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TOP_LEVEL_URL
+ );
+ const requireCorpTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TOP_LEVEL_URL + "?requirecorp"
+ );
+ const credentiallessTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TOP_LEVEL_URL + "?credentialless"
+ );
+
+ await testCache(noneTab, "include", "", noneTab, "retrieved");
+ await testCache(noneTab, "include", "", credentiallessTab, "error");
+ await testCache(noneTab, "omit", "", credentiallessTab, "retrieved");
+ await testCache(
+ noneTab,
+ "include",
+ "corp_cross_origin",
+ credentiallessTab,
+ "retrieved"
+ );
+ await testCache(noneTab, "include", "", requireCorpTab, "error");
+ await testCache(
+ noneTab,
+ "include",
+ "corp_cross_origin",
+ requireCorpTab,
+ "retrieved"
+ );
+ await testCache(credentiallessTab, "include", "", noneTab, "retrieved");
+ await testCache(
+ credentiallessTab,
+ "include",
+ "",
+ credentiallessTab,
+ "retrieved"
+ );
+ await testCache(credentiallessTab, "include", "", requireCorpTab, "error");
+ await testCache(
+ requireCorpTab,
+ "include",
+ "corp_cross_origin",
+ noneTab,
+ "retrieved"
+ );
+ await testCache(
+ requireCorpTab,
+ "include",
+ "corp_cross_origin",
+ credentiallessTab,
+ "retrieved"
+ );
+ await testCache(
+ requireCorpTab,
+ "include",
+ "corp_cross_origin",
+ requireCorpTab,
+ "retrieved"
+ );
+
+ await BrowserTestUtils.removeTab(noneTab);
+ await BrowserTestUtils.removeTab(requireCorpTab);
+ await BrowserTestUtils.removeTab(credentiallessTab);
+});
diff --git a/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_1.js b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_1.js
new file mode 100644
index 0000000000..773c15e72a
--- /dev/null
+++ b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_1.js
@@ -0,0 +1,145 @@
+const TOP_LEVEL_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "open_credentialless_document.sjs";
+
+const SAME_ORIGIN = "https://example.com";
+const CROSS_ORIGIN = "https://test1.example.com";
+
+const GET_STATE_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "store_header.sjs?getstate";
+
+async function addCookieToOrigin(origin) {
+ const fetchRequestURL =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) +
+ "store_header.sjs?addcookie";
+
+ const addcookieTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ fetchRequestURL
+ );
+
+ await SpecialPowers.spawn(addcookieTab.linkedBrowser, [], async function () {
+ content.document.cookie = "coep=credentialless; SameSite=None; Secure";
+ });
+ await BrowserTestUtils.removeTab(addcookieTab);
+}
+
+async function testOrigin(
+ fetchOrigin,
+ isCredentialless,
+ useMetaTag,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ expectedCookieResult
+) {
+ let params = [];
+ if (isCredentialless) {
+ params.push("credentialless");
+ }
+ if (useMetaTag) {
+ params.push("meta");
+ }
+
+ let topLevelUrl = TOP_LEVEL_URL;
+ if (params.length) {
+ topLevelUrl += "?" + params.join("&");
+ }
+
+ const noCredentiallessTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ topLevelUrl
+ );
+
+ const fetchRequestURL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ fetchOrigin
+ ) + "store_header.sjs?checkheader";
+
+ await SpecialPowers.spawn(
+ noCredentiallessTab.linkedBrowser,
+ [
+ !useMetaTag && isCredentialless,
+ fetchRequestURL,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ GET_STATE_URL,
+ expectedCookieResult,
+ ],
+ async function (
+ sharedArrayBufferEnabled,
+ fetchRequestURL,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ getStateURL,
+ expectedCookieResult
+ ) {
+ if (sharedArrayBufferEnabled) {
+ ok(content.crossOriginIsolated);
+ }
+ // When store_header.sjs receives this request, it will store
+ // whether it has received the cookie as a shared state.
+ await content.fetch(fetchRequestURL, {
+ mode: fetchRequestMode,
+ credentials: fetchRequestCrendentials,
+ });
+
+ // This request is used to get the saved state from the
+ // previous fetch request.
+ const response = await content.fetch(getStateURL, {
+ mode: "cors",
+ });
+ const text = await response.text();
+ is(text, expectedCookieResult);
+ }
+ );
+
+ await BrowserTestUtils.removeTab(noCredentiallessTab);
+}
+
+async function doTest(
+ origin,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ expectedCookieResultForNoCredentialless,
+ expectedCookieResultForCredentialless
+) {
+ for (let credentialless of [true, false]) {
+ for (let meta of [true, false]) {
+ await testOrigin(
+ origin,
+ credentialless,
+ meta,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ credentialless
+ ? expectedCookieResultForCredentialless
+ : expectedCookieResultForNoCredentialless
+ );
+ }
+ }
+}
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.tabs.remote.coep.credentialless", false],
+ ["dom.origin-trials.enabled", true],
+ ["dom.origin-trials.test-key.enabled", true],
+ ],
+ });
+
+ await addCookieToOrigin(SAME_ORIGIN);
+ await addCookieToOrigin(CROSS_ORIGIN);
+
+ // Cookies never sent with omit
+ await doTest(SAME_ORIGIN, "no-cors", "omit", "noCookie", "noCookie");
+ await doTest(SAME_ORIGIN, "cors", "omit", "noCookie", "noCookie");
+ await doTest(CROSS_ORIGIN, "no-cors", "omit", "noCookie", "noCookie");
+ await doTest(CROSS_ORIGIN, "cors", "omit", "noCookie", "noCookie");
+});
diff --git a/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_2.js b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_2.js
new file mode 100644
index 0000000000..7a1e6879ac
--- /dev/null
+++ b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_2.js
@@ -0,0 +1,145 @@
+const TOP_LEVEL_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "open_credentialless_document.sjs";
+
+const SAME_ORIGIN = "https://example.com";
+const CROSS_ORIGIN = "https://test1.example.com";
+
+const GET_STATE_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "store_header.sjs?getstate";
+
+async function addCookieToOrigin(origin) {
+ const fetchRequestURL =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) +
+ "store_header.sjs?addcookie";
+
+ const addcookieTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ fetchRequestURL
+ );
+
+ await SpecialPowers.spawn(addcookieTab.linkedBrowser, [], async function () {
+ content.document.cookie = "coep=credentialless; SameSite=None; Secure";
+ });
+ await BrowserTestUtils.removeTab(addcookieTab);
+}
+
+async function testOrigin(
+ fetchOrigin,
+ isCredentialless,
+ useMetaTag,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ expectedCookieResult
+) {
+ let params = [];
+ if (isCredentialless) {
+ params.push("credentialless");
+ }
+ if (useMetaTag) {
+ params.push("meta");
+ }
+
+ let topLevelUrl = TOP_LEVEL_URL;
+ if (params.length) {
+ topLevelUrl += "?" + params.join("&");
+ }
+
+ const noCredentiallessTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ topLevelUrl
+ );
+
+ const fetchRequestURL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ fetchOrigin
+ ) + "store_header.sjs?checkheader";
+
+ await SpecialPowers.spawn(
+ noCredentiallessTab.linkedBrowser,
+ [
+ !useMetaTag && isCredentialless,
+ fetchRequestURL,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ GET_STATE_URL,
+ expectedCookieResult,
+ ],
+ async function (
+ sharedArrayBufferEnabled,
+ fetchRequestURL,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ getStateURL,
+ expectedCookieResult
+ ) {
+ if (sharedArrayBufferEnabled) {
+ ok(content.crossOriginIsolated);
+ }
+ // When store_header.sjs receives this request, it will store
+ // whether it has received the cookie as a shared state.
+ await content.fetch(fetchRequestURL, {
+ mode: fetchRequestMode,
+ credentials: fetchRequestCrendentials,
+ });
+
+ // This request is used to get the saved state from the
+ // previous fetch request.
+ const response = await content.fetch(getStateURL, {
+ mode: "cors",
+ });
+ const text = await response.text();
+ is(text, expectedCookieResult);
+ }
+ );
+
+ await BrowserTestUtils.removeTab(noCredentiallessTab);
+}
+
+async function doTest(
+ origin,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ expectedCookieResultForNoCredentialless,
+ expectedCookieResultForCredentialless
+) {
+ for (let credentialless of [true, false]) {
+ for (let meta of [true, false]) {
+ await testOrigin(
+ origin,
+ credentialless,
+ meta,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ credentialless
+ ? expectedCookieResultForCredentialless
+ : expectedCookieResultForNoCredentialless
+ );
+ }
+ }
+}
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.tabs.remote.coep.credentialless", false],
+ ["dom.origin-trials.enabled", true],
+ ["dom.origin-trials.test-key.enabled", true],
+ ],
+ });
+
+ await addCookieToOrigin(SAME_ORIGIN);
+ await addCookieToOrigin(CROSS_ORIGIN);
+
+ // Same-origin request contains Cookies.
+ await doTest(SAME_ORIGIN, "no-cors", "include", "hasCookie", "hasCookie");
+ await doTest(SAME_ORIGIN, "cors", "include", "hasCookie", "hasCookie");
+ await doTest(SAME_ORIGIN, "no-cors", "same-origin", "hasCookie", "hasCookie");
+ await doTest(SAME_ORIGIN, "cors", "same-origin", "hasCookie", "hasCookie");
+});
diff --git a/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_3.js b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_3.js
new file mode 100644
index 0000000000..a1a1edb2ff
--- /dev/null
+++ b/dom/fetch/tests/browser_origin_trial_coep_credentialless_fetch_3.js
@@ -0,0 +1,150 @@
+const TOP_LEVEL_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "open_credentialless_document.sjs";
+
+const SAME_ORIGIN = "https://example.com";
+const CROSS_ORIGIN = "https://test1.example.com";
+
+const GET_STATE_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "store_header.sjs?getstate";
+
+async function addCookieToOrigin(origin) {
+ const fetchRequestURL =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) +
+ "store_header.sjs?addcookie";
+
+ const addcookieTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ fetchRequestURL
+ );
+
+ await SpecialPowers.spawn(addcookieTab.linkedBrowser, [], async function () {
+ content.document.cookie = "coep=credentialless; SameSite=None; Secure";
+ });
+ await BrowserTestUtils.removeTab(addcookieTab);
+}
+
+async function testOrigin(
+ fetchOrigin,
+ isCredentialless,
+ useMetaTag,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ expectedCookieResult
+) {
+ let params = [];
+ if (isCredentialless) {
+ params.push("credentialless");
+ }
+ if (useMetaTag) {
+ params.push("meta");
+ }
+
+ let topLevelUrl = TOP_LEVEL_URL;
+ if (params.length) {
+ topLevelUrl += "?" + params.join("&");
+ }
+
+ const noCredentiallessTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ topLevelUrl
+ );
+
+ const fetchRequestURL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ fetchOrigin
+ ) + "store_header.sjs?checkheader";
+
+ await SpecialPowers.spawn(
+ noCredentiallessTab.linkedBrowser,
+ [
+ !useMetaTag && isCredentialless,
+ fetchRequestURL,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ GET_STATE_URL,
+ expectedCookieResult,
+ ],
+ async function (
+ sharedArrayBufferEnabled,
+ fetchRequestURL,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ getStateURL,
+ expectedCookieResult
+ ) {
+ if (sharedArrayBufferEnabled) {
+ ok(content.crossOriginIsolated);
+ }
+ // When store_header.sjs receives this request, it will store
+ // whether it has received the cookie as a shared state.
+ await content.fetch(fetchRequestURL, {
+ mode: fetchRequestMode,
+ credentials: fetchRequestCrendentials,
+ });
+
+ // This request is used to get the saved state from the
+ // previous fetch request.
+ const response = await content.fetch(getStateURL, {
+ mode: "cors",
+ });
+ const text = await response.text();
+ is(text, expectedCookieResult);
+ }
+ );
+
+ await BrowserTestUtils.removeTab(noCredentiallessTab);
+}
+
+async function doTest(
+ origin,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ expectedCookieResultForNoCredentialless,
+ expectedCookieResultForCredentialless
+) {
+ for (let credentialless of [true, false]) {
+ for (let meta of [true, false]) {
+ await testOrigin(
+ origin,
+ credentialless,
+ meta,
+ fetchRequestMode,
+ fetchRequestCrendentials,
+ credentialless
+ ? expectedCookieResultForCredentialless
+ : expectedCookieResultForNoCredentialless
+ );
+ }
+ }
+}
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.tabs.remote.coep.credentialless", false],
+ ["dom.origin-trials.enabled", true],
+ ["dom.origin-trials.test-key.enabled", true],
+ ],
+ });
+
+ await addCookieToOrigin(SAME_ORIGIN);
+ await addCookieToOrigin(CROSS_ORIGIN);
+
+ // Cross-origin CORS requests contains Cookies, if credentials mode is set to
+ // 'include'. This does not depends on COEP.
+ await doTest(CROSS_ORIGIN, "cors", "include", "hasCookie", "hasCookie");
+ await doTest(CROSS_ORIGIN, "cors", "same-origin", "noCookie", "noCookie");
+
+ // Cross-origin no-CORS requests includes Cookies when:
+ // 1. credentials mode is 'include'
+ // 2. COEP: is not credentialless.
+ await doTest(CROSS_ORIGIN, "no-cors", "include", "hasCookie", "noCookie");
+ await doTest(CROSS_ORIGIN, "no-cors", "same-origin", "noCookie", "noCookie");
+});
diff --git a/dom/fetch/tests/browser_origin_trial_coep_credentialless_worker.js b/dom/fetch/tests/browser_origin_trial_coep_credentialless_worker.js
new file mode 100644
index 0000000000..be77add88e
--- /dev/null
+++ b/dom/fetch/tests/browser_origin_trial_coep_credentialless_worker.js
@@ -0,0 +1,164 @@
+const TOP_LEVEL_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "open_credentialless_document.sjs";
+
+const WORKER_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "credentialless_worker.sjs";
+
+const GET_STATE_URL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "store_header.sjs?getstate";
+
+const SAME_ORIGIN = "https://example.com";
+const CROSS_ORIGIN = "https://test1.example.com";
+
+const WORKER_USES_CREDENTIALLESS = "credentialless";
+const WORKER_NOT_USE_CREDENTIALLESS = "";
+
+async function addCookieToOrigin(origin) {
+ const fetchRequestURL =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) +
+ "store_header.sjs?addcookie";
+
+ const addcookieTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ fetchRequestURL
+ );
+
+ await SpecialPowers.spawn(addcookieTab.linkedBrowser, [], async function () {
+ content.document.cookie = "coep=credentialless; SameSite=None; Secure";
+ });
+ await BrowserTestUtils.removeTab(addcookieTab);
+}
+
+async function testOrigin(
+ fetchOrigin,
+ isCredentialless,
+ workerUsesCredentialless,
+ expectedCookieResult
+) {
+ let topLevelUrl = TOP_LEVEL_URL;
+ if (isCredentialless) {
+ topLevelUrl += "?credentialless";
+ }
+ const noCredentiallessTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ topLevelUrl
+ );
+
+ const fetchRequestURL =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ fetchOrigin
+ ) + "store_header.sjs?checkheader";
+
+ let workerScriptURL = WORKER_URL + "?" + workerUsesCredentialless;
+
+ await SpecialPowers.spawn(
+ noCredentiallessTab.linkedBrowser,
+ [fetchRequestURL, GET_STATE_URL, workerScriptURL, expectedCookieResult],
+ async function (
+ fetchRequestURL,
+ getStateURL,
+ workerScriptURL,
+ expectedCookieResult
+ ) {
+ const worker = new content.Worker(workerScriptURL, {});
+
+ // When the worker receives this message, it'll send
+ // a fetch request to fetchRequestURL, and fetchRequestURL
+ // will store whether it has received the cookie as a
+ // shared state.
+ worker.postMessage(fetchRequestURL);
+
+ if (expectedCookieResult == "error") {
+ await new Promise(r => {
+ worker.onerror = function () {
+ ok(true, "worker has error");
+ r();
+ };
+ });
+ } else {
+ await new Promise(r => {
+ worker.addEventListener("message", async function () {
+ // This request is used to get the saved state from the
+ // previous fetch request.
+ const response = await content.fetch(getStateURL, {
+ mode: "cors",
+ });
+ const text = await response.text();
+ is(text, expectedCookieResult);
+ r();
+ });
+ });
+ }
+ }
+ );
+ await BrowserTestUtils.removeTab(noCredentiallessTab);
+}
+
+async function dedicatedWorkerTest(
+ origin,
+ workerCOEP,
+ expectedCookieResultForNoCredentialless,
+ expectedCookieResultForCredentialless
+) {
+ await testOrigin(
+ origin,
+ false,
+ workerCOEP,
+ expectedCookieResultForNoCredentialless
+ );
+ await testOrigin(
+ origin,
+ true,
+ workerCOEP,
+ expectedCookieResultForCredentialless
+ );
+}
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.tabs.remote.coep.credentialless", false], // Explicitly set credentialless to false because we want to test origin trial
+ ["dom.origin-trials.enabled", true],
+ ["dom.origin-trials.test-key.enabled", true],
+ ],
+ });
+
+ await addCookieToOrigin(SAME_ORIGIN);
+ await addCookieToOrigin(CROSS_ORIGIN);
+
+ await dedicatedWorkerTest(
+ SAME_ORIGIN,
+ WORKER_NOT_USE_CREDENTIALLESS,
+ "hasCookie",
+ "error"
+ );
+ await dedicatedWorkerTest(
+ SAME_ORIGIN,
+ WORKER_USES_CREDENTIALLESS,
+ "hasCookie",
+ "hasCookie"
+ );
+
+ await dedicatedWorkerTest(
+ CROSS_ORIGIN,
+ WORKER_NOT_USE_CREDENTIALLESS,
+ "hasCookie",
+ "error"
+ );
+ await dedicatedWorkerTest(
+ CROSS_ORIGIN,
+ WORKER_USES_CREDENTIALLESS,
+ "noCookie",
+ "noCookie"
+ );
+});
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..b033d842fa
--- /dev/null
+++ b/dom/fetch/tests/crashtests/1664514.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<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..f79a122734
--- /dev/null
+++ b/dom/fetch/tests/crashtests/crashtests.list
@@ -0,0 +1,2 @@
+load 1577196.html
+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/credentialless_resource.sjs b/dom/fetch/tests/credentialless_resource.sjs
new file mode 100644
index 0000000000..72d0d738ec
--- /dev/null
+++ b/dom/fetch/tests/credentialless_resource.sjs
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+// small red image
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
+);
+
+function handleRequest(request, response) {
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: image/png\r\n");
+ if (request.queryString === "corp_cross_origin") {
+ response.write("Cross-Origin-Resource-Policy: cross-origin\r\n");
+ }
+ response.write("\r\n");
+ response.write(IMG_BYTES);
+ response.finish();
+}
diff --git a/dom/fetch/tests/credentialless_worker.sjs b/dom/fetch/tests/credentialless_worker.sjs
new file mode 100644
index 0000000000..a9e2197d18
--- /dev/null
+++ b/dom/fetch/tests/credentialless_worker.sjs
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const WORKER = `
+ onmessage = function(event) {
+ fetch(event.data, {
+ mode: "no-cors",
+ credentials: "include"
+ }).then(function() {
+ postMessage("fetch done");
+ });
+ }
+`;
+
+function handleRequest(request, response) {
+ if (request.queryString === "credentialless") {
+ response.setHeader("Cross-Origin-Embedder-Policy", "credentialless", true);
+ }
+
+ response.setHeader("Content-Type", "application/javascript", false);
+ response.setStatusLine(request.httpVersion, "200", "Found");
+ response.write(WORKER);
+}
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/open_credentialless_document.sjs b/dom/fetch/tests/open_credentialless_document.sjs
new file mode 100644
index 0000000000..6269e73d78
--- /dev/null
+++ b/dom/fetch/tests/open_credentialless_document.sjs
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Created with: mktoken --origin 'https://example.com' --feature CoepCredentialless --expiry 'Wed, 01 Jan 3000 01:00:00 +0100' --sign test-keys/test-ecdsa.pkcs8
+const TOKEN =
+ "Az+DK2Kczk8Xz1cAlD+TkvPZmuM2uJZ2CFefbp2hLuCU9FbUqxWTyQ2tEYr50r0syKELcOZLAPaABw8aYTLHn5YAAABUeyJvcmlnaW4iOiJodHRwczovL2V4YW1wbGUuY29tIiwiZmVhdHVyZSI6IkNvZXBDcmVkZW50aWFsbGVzcyIsImV4cGlyeSI6MzI1MDM2ODAwMDB9";
+
+function handleRequest(request, response) {
+ let params = (request.queryString || "").split("&");
+ if (params.includes("credentialless")) {
+ response.setHeader("Cross-Origin-Embedder-Policy", "credentialless");
+ // Enables the SharedArrayBuffer feature
+ response.setHeader("Cross-Origin-Opener-Policy", "same-origin");
+ } else if (params.includes("requirecorp")) {
+ response.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
+ }
+ let html = "<!doctype html>";
+ if (!params.includes("meta")) {
+ response.setHeader("Origin-Trial", TOKEN);
+ } else {
+ html += `<meta http-equiv="origin-trial" content="${TOKEN}">`;
+ }
+ html += "<body>Hello, world!</body>";
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+ response.setStatusLine(request.httpVersion, "200", "Found");
+ response.write(html);
+}
diff --git a/dom/fetch/tests/store_header.sjs b/dom/fetch/tests/store_header.sjs
new file mode 100644
index 0000000000..3290a09995
--- /dev/null
+++ b/dom/fetch/tests/store_header.sjs
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const key = "store_header";
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Access-Control-Allow-Origin", "https://example.com");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+
+ if (request.queryString === "getstate") {
+ response.write(getSharedState(key));
+ } else if (request.queryString === "checkheader") {
+ if (request.hasHeader("Cookie")) {
+ setSharedState(key, "hasCookie");
+ } else {
+ setSharedState(key, "noCookie");
+ }
+ } else {
+ // This is the first request which sets the cookie
+ }
+}
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..e4b6cf4eb2
--- /dev/null
+++ b/dom/fetch/tests/test_ext_response_constructor.html
@@ -0,0 +1,47 @@
+<!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() {
+ /* eslint-env webextensions */
+ 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..710afffcc6
--- /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 (let [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>