/* -*- 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 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 mReadableStreamBody; }; template class FetchBody : public FetchBodyBase, public AbortFollower { public: using FetchBodyBase::QueryInterface; NS_INLINE_DECL_REFCOUNTING_INHERITED(FetchBody, FetchBodyBase) bool BodyUsed() const; already_AddRefed ArrayBuffer(JSContext* aCx, ErrorResult& aRv) { return ConsumeBody(aCx, BodyConsumer::CONSUME_ARRAYBUFFER, aRv); } already_AddRefed Blob(JSContext* aCx, ErrorResult& aRv) { return ConsumeBody(aCx, BodyConsumer::CONSUME_BLOB, aRv); } already_AddRefed FormData(JSContext* aCx, ErrorResult& aRv) { return ConsumeBody(aCx, BodyConsumer::CONSUME_FORMDATA, aRv); } already_AddRefed Json(JSContext* aCx, ErrorResult& aRv) { return ConsumeBody(aCx, BodyConsumer::CONSUME_JSON, aRv); } already_AddRefed Text(JSContext* aCx, ErrorResult& aRv) { return ConsumeBody(aCx, BodyConsumer::CONSUME_TEXT, aRv); } already_AddRefed 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 ConsumeBody(JSContext* aCx, BodyConsumer::ConsumeType aType, ErrorResult& aRv); protected: nsCOMPtr mOwner; // This is the Reader used to retrieve data from the body. This needs to be // traversed by subclasses. RefPtr mFetchStreamReader; explicit FetchBody(nsIGlobalObject* aOwner); virtual ~FetchBody(); void SetReadableStreamBody(JSContext* aCx, ReadableStream* aBody); private: Derived* DerivedClass() const { return static_cast(const_cast(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 mMainThreadEventTarget; }; class EmptyBody final : public FetchBody { NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(EmptyBody, FetchBody) public: static already_AddRefed 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& 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 aBodyStream); ~EmptyBody(); UniquePtr mPrincipalInfo; RefPtr mAbortSignalImpl; nsCString mMimeType; nsCString mMixedCaseMimeType; nsCOMPtr mBodyStream; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_Fetch_h