/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* 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_UnderlyingSourceCallbackHelpers_h #define mozilla_dom_UnderlyingSourceCallbackHelpers_h #include "mozilla/DOMEventTargetHelper.h" #include "mozilla/HoldDropJSObjects.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/UnderlyingSourceBinding.h" #include "mozilla/WeakPtr.h" #include "nsIAsyncInputStream.h" #include "nsISupports.h" #include "nsISupportsImpl.h" /* Since the streams specification has native descriptions of some callbacks * (i.e. described in prose, rather than provided by user code), we need to be * able to pass around native callbacks. To handle this, we define polymorphic * classes That cover the difference between native callback and user-provided. * * The Streams specification wants us to invoke these callbacks, run through * WebIDL as if they were methods. So we have to preserve the underlying object * to use as the This value on invocation. */ enum class nsresult : uint32_t; namespace mozilla::dom { class StrongWorkerRef; class BodyStreamHolder; class ReadableStreamController; class ReadableStream; class UnderlyingSourceAlgorithmsBase : public nsISupports { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(UnderlyingSourceAlgorithmsBase) MOZ_CAN_RUN_SCRIPT virtual void StartCallback( JSContext* aCx, ReadableStreamController& aController, JS::MutableHandle aRetVal, ErrorResult& aRv) = 0; // A promise-returning algorithm that pulls data from the underlying byte // source MOZ_CAN_RUN_SCRIPT virtual already_AddRefed PullCallback( JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) = 0; // A promise-returning algorithm, taking one argument (the cancel reason), // which communicates a requested cancelation to the underlying byte source MOZ_CAN_RUN_SCRIPT virtual already_AddRefed CancelCallback( JSContext* aCx, const Optional>& aReason, ErrorResult& aRv) = 0; // Implement this when you need to release underlying resources immediately // from closed(canceled)/errored streams, without waiting for GC. virtual void ReleaseObjects() {} // Fetch wants to special-case nsIInputStream-based streams virtual nsIInputStream* MaybeGetInputStreamIfUnread() { return nullptr; } // https://streams.spec.whatwg.org/#other-specs-rs-create // By "native" we mean "instances initialized via the above set up or set up // with byte reading support algorithms (not, e.g., on web-developer-created // instances)" virtual bool IsNative() { return true; } protected: virtual ~UnderlyingSourceAlgorithmsBase() = default; }; class UnderlyingSourceAlgorithms final : public UnderlyingSourceAlgorithmsBase { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED( UnderlyingSourceAlgorithms, UnderlyingSourceAlgorithmsBase) UnderlyingSourceAlgorithms(nsIGlobalObject* aGlobal, JS::Handle aUnderlyingSource, UnderlyingSource& aUnderlyingSourceDict) : mGlobal(aGlobal), mUnderlyingSource(aUnderlyingSource) { // Step 6. (implicit Step 2.) if (aUnderlyingSourceDict.mStart.WasPassed()) { mStartCallback = aUnderlyingSourceDict.mStart.Value(); } // Step 7. (implicit Step 3.) if (aUnderlyingSourceDict.mPull.WasPassed()) { mPullCallback = aUnderlyingSourceDict.mPull.Value(); } // Step 8. (implicit Step 4.) if (aUnderlyingSourceDict.mCancel.WasPassed()) { mCancelCallback = aUnderlyingSourceDict.mCancel.Value(); } mozilla::HoldJSObjects(this); }; MOZ_CAN_RUN_SCRIPT void StartCallback(JSContext* aCx, ReadableStreamController& aController, JS::MutableHandle aRetVal, ErrorResult& aRv) override; MOZ_CAN_RUN_SCRIPT already_AddRefed PullCallback( JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) override; MOZ_CAN_RUN_SCRIPT already_AddRefed CancelCallback( JSContext* aCx, const Optional>& aReason, ErrorResult& aRv) override; bool IsNative() override { return false; } protected: ~UnderlyingSourceAlgorithms() override { mozilla::DropJSObjects(this); }; private: // Virtually const, but are cycle collected nsCOMPtr mGlobal; JS::Heap mUnderlyingSource; MOZ_KNOWN_LIVE RefPtr mStartCallback; MOZ_KNOWN_LIVE RefPtr mPullCallback; MOZ_KNOWN_LIVE RefPtr mCancelCallback; }; // https://streams.spec.whatwg.org/#readablestream-set-up // https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support // Wrappers defined by the "Set up" methods in the spec. This helps you just // return nullptr when an error occurred as this wrapper converts it to a // rejected promise. // Note that StartCallback is only for JS consumers to access // the controller, and thus is no-op here since native consumers can call // `EnqueueNative()` etc. without direct controller access. class UnderlyingSourceAlgorithmsWrapper : public UnderlyingSourceAlgorithmsBase { void StartCallback(JSContext*, ReadableStreamController&, JS::MutableHandle aRetVal, ErrorResult&) final; MOZ_CAN_RUN_SCRIPT already_AddRefed PullCallback( JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) final; MOZ_CAN_RUN_SCRIPT already_AddRefed CancelCallback( JSContext* aCx, const Optional>& aReason, ErrorResult& aRv) final; MOZ_CAN_RUN_SCRIPT virtual already_AddRefed PullCallbackImpl( JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) { // pullAlgorithm is optional, return null by default return nullptr; } virtual already_AddRefed CancelCallbackImpl( JSContext* aCx, const Optional>& aReason, ErrorResult& aRv) { // cancelAlgorithm is optional, return null by default return nullptr; } }; class InputToReadableStreamAlgorithms; // This class exists to isolate InputToReadableStreamAlgorithms from the // nsIAsyncInputStream. If we call AsyncWait(this,...), it holds a // reference to 'this' which can't be cc'd, and we can leak the stream, // causing a Worker to assert with globalScopeAlive. By isolating // ourselves from the inputstream, we can safely be CC'd if needed and // will inform the inputstream to shut down. class InputStreamHolder final : public nsIInputStreamCallback, public GlobalTeardownObserver { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIINPUTSTREAMCALLBACK InputStreamHolder(nsIGlobalObject* aGlobal, InputToReadableStreamAlgorithms* aCallback, nsIAsyncInputStream* aInput); void Init(JSContext* aCx); void DisconnectFromOwner() override; // Used by global teardown void Shutdown(); // These just proxy the calls to the nsIAsyncInputStream nsresult AsyncWait(uint32_t aFlags, uint32_t aRequestedCount, nsIEventTarget* aEventTarget); nsresult Available(uint64_t* aSize) { return mInput->Available(aSize); } nsresult Read(char* aBuffer, uint32_t aLength, uint32_t* aWritten) { return mInput->Read(aBuffer, aLength, aWritten); } nsresult CloseWithStatus(nsresult aStatus) { return mInput->CloseWithStatus(aStatus); } private: ~InputStreamHolder(); // WeakPtr to avoid cycles WeakPtr mCallback; // To ensure the worker sticks around RefPtr mAsyncWaitWorkerRef; RefPtr mWorkerRef; nsCOMPtr mInput; // To ensure the underlying source sticks around during an ongoing read // operation. mAlgorithms is not cycle collected on purpose, and this holder // is responsible to keep the underlying source algorithms until // nsIAsyncInputStream responds. // // This is done because otherwise the whole stream objects may be cycle // collected, including the promises created from read(), as our JS engine may // throw unsettled promises away for optimization. See bug 1849860. RefPtr mAsyncWaitAlgorithms; }; // Using this class means you are also passing the lifetime control of your // nsIAsyncInputStream, as it will be closed when this class tears down. class InputToReadableStreamAlgorithms final : public UnderlyingSourceAlgorithmsWrapper, public nsIInputStreamCallback, public SupportsWeakPtr { NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIINPUTSTREAMCALLBACK NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InputToReadableStreamAlgorithms, UnderlyingSourceAlgorithmsWrapper) InputToReadableStreamAlgorithms(JSContext* aCx, nsIAsyncInputStream* aInput, ReadableStream* aStream); // Streams algorithms already_AddRefed PullCallbackImpl( JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) override; void ReleaseObjects() override; private: ~InputToReadableStreamAlgorithms() { if (mInput) { mInput->Shutdown(); } } MOZ_CAN_RUN_SCRIPT_BOUNDARY void CloseAndReleaseObjects( JSContext* aCx, ReadableStream* aStream); void WriteIntoReadRequestBuffer(JSContext* aCx, ReadableStream* aStream, JS::Handle aBuffer, uint32_t aLength, uint32_t* aByteWritten); MOZ_CAN_RUN_SCRIPT_BOUNDARY void EnqueueChunkWithSizeIntoStream( JSContext* aCx, ReadableStream* aStream, uint64_t aAvailableData, ErrorResult& aRv); void ErrorPropagation(JSContext* aCx, ReadableStream* aStream, nsresult aError); // Common methods bool IsClosed() { return !mInput; } nsCOMPtr mOwningEventTarget; // This promise is created by PullCallback and resolved when // OnInputStreamReady succeeds. No need to try hard to settle it though, see // also ReleaseObjects() for the reason. RefPtr mPullPromise; RefPtr mInput; RefPtr mStream; }; class NonAsyncInputToReadableStreamAlgorithms : public UnderlyingSourceAlgorithmsWrapper { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( NonAsyncInputToReadableStreamAlgorithms, UnderlyingSourceAlgorithmsWrapper) explicit NonAsyncInputToReadableStreamAlgorithms(nsIInputStream& aInput) : mInput(&aInput) {} already_AddRefed PullCallbackImpl( JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) override; void ReleaseObjects() override { if (RefPtr algorithms = mAsyncAlgorithms.forget()) { algorithms->ReleaseObjects(); } if (nsCOMPtr input = mInput.forget()) { input->Close(); } } nsIInputStream* MaybeGetInputStreamIfUnread() override { MOZ_ASSERT(mInput, "Should be only called on non-disturbed streams"); return mInput; } private: ~NonAsyncInputToReadableStreamAlgorithms() = default; nsCOMPtr mInput; RefPtr mAsyncAlgorithms; }; } // namespace mozilla::dom #endif