diff options
Diffstat (limited to 'dom/fetch/Fetch.h')
-rw-r--r-- | dom/fetch/Fetch.h | 310 |
1 files changed, 310 insertions, 0 deletions
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 |