Create the body ReadableStream from the underlying body input stream directly. When clone() is called before the stream has been read and the underlying body is not cloneable, clone() replaces the original input stream; repoint the existing unread native ReadableStream at the replacement so the original stream is not read from two places. This keeps native bodies native (cancellation of an unread stream still tears down the channel, and the consume path keeps reading the body input stream directly) without teeing native byte streams. This no longer clones the body when creating the ReadableStream, so it does not buffer the whole body; restore the huge-fetch crashtest expectations that were disabled to avoid OOMing the 32-bit process. Differential Revision: https://phabricator.services.mozilla.com/D280568
533 lines
17 KiB
C++
533 lines
17 KiB
C++
/* 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 "mozilla/ErrorResult.h"
|
|
#include "mozilla/StaticPrefs_network.h"
|
|
#include "mozilla/dom/Fetch.h"
|
|
#include "mozilla/dom/FetchUtil.h"
|
|
#include "mozilla/dom/Headers.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/dom/ReadableStreamDefaultReader.h"
|
|
#include "mozilla/dom/URL.h"
|
|
#include "mozilla/dom/WindowContext.h"
|
|
#include "mozilla/dom/WorkerPrivate.h"
|
|
#include "mozilla/dom/WorkerRunnable.h"
|
|
#include "mozilla/ipc/PBackgroundSharedTypes.h"
|
|
#include "nsIURI.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPIDOMWindow.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(mGlobal)
|
|
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(mGlobal)
|
|
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* aGlobal, SafeRefPtr<InternalRequest> aRequest,
|
|
AbortSignal* aSignal)
|
|
: FetchBody<Request>(aGlobal), 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.
|
|
AutoTArray<OwningNonNull<AbortSignal>, 1> array{OwningNonNull(*aSignal)};
|
|
mSignal = AbortSignal::Any(aGlobal, array, [](nsIGlobalObject* aGlobal) {
|
|
return AbortSignal::Create(aGlobal, SignalAborted::No,
|
|
JS::UndefinedHandleValue);
|
|
});
|
|
}
|
|
}
|
|
|
|
Request::~Request() = default;
|
|
|
|
SafeRefPtr<InternalRequest> Request::GetInternalRequest() {
|
|
return mRequest.clonePtr();
|
|
}
|
|
|
|
namespace {
|
|
already_AddRefed<nsIURI> ParseURL(nsIGlobalObject* aGlobal,
|
|
const nsACString& aInput, ErrorResult& aRv) {
|
|
nsCOMPtr<nsIURI> baseURI;
|
|
if (NS_IsMainThread()) {
|
|
nsCOMPtr<nsPIDOMWindowInner> inner(do_QueryInterface(aGlobal));
|
|
Document* doc = inner ? inner->GetExtantDoc() : nullptr;
|
|
baseURI = doc ? doc->GetBaseURI() : nullptr;
|
|
} else {
|
|
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
|
|
baseURI = worker->GetBaseURI();
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aInput, nullptr, baseURI))) {
|
|
aRv.ThrowTypeError<MSG_INVALID_URL>(aInput);
|
|
return nullptr;
|
|
}
|
|
return uri.forget();
|
|
}
|
|
|
|
void GetRequestURL(nsIGlobalObject* aGlobal, const nsACString& aInput,
|
|
nsIURI** aRequestURL, nsACString& aURLfragment,
|
|
ErrorResult& aRv) {
|
|
nsCOMPtr<nsIURI> resolvedURI = ParseURL(aGlobal, 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;
|
|
(void)resolvedURI->GetUserPass(credentials);
|
|
if (!credentials.IsEmpty()) {
|
|
aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(aInput);
|
|
return;
|
|
}
|
|
|
|
aRv = NS_GetURIWithoutRef(resolvedURI, aRequestURL);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return;
|
|
}
|
|
|
|
// Get the fragment from nsIURI.
|
|
aRv = resolvedURI->GetRef(aURLfragment);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
/*static*/
|
|
SafeRefPtr<Request> Request::Constructor(const GlobalObject& aGlobal,
|
|
const RequestOrUTF8String& aInput,
|
|
const RequestInit& aInit,
|
|
ErrorResult& aRv) {
|
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
|
return Constructor(global, aGlobal.Context(), aInput, aInit,
|
|
aGlobal.CallerType(), aRv);
|
|
}
|
|
|
|
/*static*/
|
|
SafeRefPtr<Request> Request::Constructor(
|
|
nsIGlobalObject* aGlobal, JSContext* aCx, const RequestOrUTF8String& aInput,
|
|
const RequestInit& aInit, CallerType aCallerType, ErrorResult& aRv) {
|
|
bool hasCopiedBody = false;
|
|
SafeRefPtr<InternalRequest> request;
|
|
|
|
RefPtr<AbortSignal> signal;
|
|
bool bodyFromInit = false;
|
|
|
|
if (aInput.IsRequest()) {
|
|
RefPtr<Request> inputReq = &aInput.GetAsRequest();
|
|
nsCOMPtr<nsIInputStream> body;
|
|
|
|
if (aInit.mBody.WasPassed() && !aInit.mBody.Value().IsNull()) {
|
|
bodyFromInit = true;
|
|
hasCopiedBody = true;
|
|
} else {
|
|
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 UTF8String.
|
|
// We need to get url before we create a InternalRequest.
|
|
const nsACString& input = aInput.GetAsUTF8String();
|
|
nsCOMPtr<nsIURI> requestURL;
|
|
nsCString fragment;
|
|
GetRequestURL(aGlobal, input, getter_AddRefs(requestURL), fragment, aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
request = MakeSafeRefPtr<InternalRequest>(WrapNotNull(requestURL.get()),
|
|
fragment);
|
|
}
|
|
request = request->GetRequestConstructorCopy(aGlobal, aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
Maybe<RequestMode> mode;
|
|
if (aInit.mMode.WasPassed()) {
|
|
if (aInit.mMode.Value() == RequestMode::Navigate) {
|
|
aRv.ThrowTypeError<MSG_INVALID_REQUEST_MODE>("navigate");
|
|
return nullptr;
|
|
}
|
|
|
|
mode.emplace(aInit.mMode.Value());
|
|
}
|
|
Maybe<RequestCredentials> credentials;
|
|
if (aInit.mCredentials.WasPassed()) {
|
|
credentials.emplace(aInit.mCredentials.Value());
|
|
}
|
|
Maybe<RequestCache> cache;
|
|
if (aInit.mCache.WasPassed()) {
|
|
cache.emplace(aInit.mCache.Value());
|
|
}
|
|
if (aInput.IsUTF8String()) {
|
|
if (mode.isNothing()) {
|
|
mode.emplace(RequestMode::Cors);
|
|
}
|
|
if (credentials.isNothing()) {
|
|
if (aCallerType == CallerType::System &&
|
|
StaticPrefs::network_fetch_systemDefaultsToOmittingCredentials()) {
|
|
credentials.emplace(RequestCredentials::Omit);
|
|
} else {
|
|
credentials.emplace(RequestCredentials::Same_origin);
|
|
}
|
|
}
|
|
if (cache.isNothing()) {
|
|
cache.emplace(RequestCache::Default);
|
|
}
|
|
}
|
|
|
|
if (aInit.IsAnyMemberPresent() && request->Mode() == RequestMode::Navigate) {
|
|
mode = Some(RequestMode::Same_origin);
|
|
}
|
|
|
|
if (aInit.IsAnyMemberPresent()) {
|
|
request->SetReferrer(nsLiteralCString(kFETCH_CLIENT_REFERRER_STR));
|
|
request->SetReferrerPolicy(ReferrerPolicy::_empty);
|
|
}
|
|
if (aInit.mReferrer.WasPassed()) {
|
|
const nsCString& referrer = aInit.mReferrer.Value();
|
|
if (referrer.IsEmpty()) {
|
|
request->SetReferrer(""_ns);
|
|
} else {
|
|
nsCOMPtr<nsIURI> referrerURI = ParseURL(aGlobal, referrer, aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer);
|
|
return nullptr;
|
|
}
|
|
|
|
nsAutoCString spec;
|
|
referrerURI->GetSpec(spec);
|
|
if (!spec.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
|
|
nsCOMPtr<nsIPrincipal> principal = aGlobal->PrincipalOrNull();
|
|
if (principal) {
|
|
nsresult rv =
|
|
principal->CheckMayLoad(referrerURI,
|
|
/* allowIfInheritsPrincipal */ false);
|
|
if (NS_FAILED(rv)) {
|
|
spec.AssignLiteral(kFETCH_CLIENT_REFERRER_STR);
|
|
}
|
|
}
|
|
}
|
|
|
|
request->SetReferrer(spec);
|
|
}
|
|
}
|
|
|
|
if (aInit.mReferrerPolicy.WasPassed()) {
|
|
request->SetReferrerPolicy(aInit.mReferrerPolicy.Value());
|
|
}
|
|
|
|
if (aInit.mSignal.WasPassed()) {
|
|
signal = aInit.mSignal.Value();
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-global-fetch
|
|
// https://fetch.spec.whatwg.org/#dom-request
|
|
// The priority of init overrides input's priority.
|
|
if (aInit.mPriority.WasPassed()) {
|
|
request->SetPriorityMode(aInit.mPriority.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.isSome()) {
|
|
request->SetMode(mode.value());
|
|
}
|
|
|
|
if (credentials.isSome()) {
|
|
request->SetCredentialsMode(credentials.value());
|
|
}
|
|
|
|
if (cache.isSome()) {
|
|
if (cache.value() == RequestCache::Only_if_cached &&
|
|
request->Mode() != RequestMode::Same_origin) {
|
|
aRv.ThrowTypeError<MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN>(
|
|
GetEnumString(request->Mode()));
|
|
return nullptr;
|
|
}
|
|
request->SetCacheMode(cache.value());
|
|
}
|
|
|
|
if (aInit.mRedirect.WasPassed()) {
|
|
request->SetRedirectMode(aInit.mRedirect.Value());
|
|
}
|
|
|
|
if (aInit.mIntegrity.WasPassed()) {
|
|
request->SetIntegrity(aInit.mIntegrity.Value());
|
|
}
|
|
|
|
if (aInit.mKeepalive.WasPassed()) {
|
|
request->SetKeepalive(aInit.mKeepalive.Value());
|
|
}
|
|
|
|
if (aInit.mMozErrors.WasPassed() && aInit.mMozErrors.Value()) {
|
|
request->SetMozErrors();
|
|
}
|
|
|
|
if (aInit.mTriggeringPrincipal.WasPassed() &&
|
|
aInit.mTriggeringPrincipal.Value()) {
|
|
request->SetTriggeringPrincipal(aInit.mTriggeringPrincipal.Value());
|
|
}
|
|
|
|
if (aInit.mNeverTaint.WasPassed()) {
|
|
if (!XRE_IsParentProcess()) {
|
|
aRv.ThrowNotAllowedError(
|
|
"Taint has to happen outside of the parent process.");
|
|
return nullptr;
|
|
}
|
|
request->SetNeverTaint(aInit.mNeverTaint.Value());
|
|
}
|
|
|
|
if (aInit.mCookieJarSettings.WasPassed() &&
|
|
aInit.mCookieJarSettings.Value()) {
|
|
request->SetCookieJarSettings(aInit.mCookieJarSettings.Value());
|
|
}
|
|
|
|
// 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();
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// Step 33. Remove privileged no-CORS request-headers, if any member presented
|
|
// in aInit.
|
|
if (aInit.IsAnyMemberPresent()) {
|
|
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();
|
|
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() && !bodyFromInit) {
|
|
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;
|
|
}
|
|
|
|
// InternalRequest::Clone() may have replaced our underlying input stream (a
|
|
// non-cloneable body is now consumed by the cloning copy). If an unread
|
|
// native ReadableStream still reflects this request's body, repoint it at the
|
|
// current stream so the original is not read from two places.
|
|
MaybeRebindReadableStreamBody();
|
|
|
|
return MakeSafeRefPtr<Request>(mGlobal, std::move(ir), GetOrCreateSignal());
|
|
}
|
|
|
|
Headers* Request::Headers_() {
|
|
if (!mHeaders) {
|
|
mHeaders = new Headers(mGlobal, mRequest->Headers());
|
|
}
|
|
|
|
return mHeaders;
|
|
}
|
|
|
|
AbortSignal* Request::GetOrCreateSignal() {
|
|
if (!mSignal) {
|
|
mSignal = AbortSignal::Create(mGlobal, SignalAborted::No,
|
|
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
|