UnderlyingSourceCallbackHelpers.h (12509B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_dom_UnderlyingSourceCallbackHelpers_h 8 #define mozilla_dom_UnderlyingSourceCallbackHelpers_h 9 10 #include "mozilla/DOMEventTargetHelper.h" 11 #include "mozilla/HoldDropJSObjects.h" 12 #include "mozilla/WeakPtr.h" 13 #include "mozilla/dom/Promise.h" 14 #include "mozilla/dom/UnderlyingSourceBinding.h" 15 #include "nsIAsyncInputStream.h" 16 #include "nsISupports.h" 17 #include "nsISupportsImpl.h" 18 19 /* Since the streams specification has native descriptions of some callbacks 20 * (i.e. described in prose, rather than provided by user code), we need to be 21 * able to pass around native callbacks. To handle this, we define polymorphic 22 * classes That cover the difference between native callback and user-provided. 23 * 24 * The Streams specification wants us to invoke these callbacks, run through 25 * WebIDL as if they were methods. So we have to preserve the underlying object 26 * to use as the This value on invocation. 27 */ 28 enum class nsresult : uint32_t; 29 30 namespace mozilla::dom { 31 32 class StrongWorkerRef; 33 class BodyStreamHolder; 34 class ReadableStreamControllerBase; 35 class ReadableStream; 36 37 class UnderlyingSourceAlgorithmsBase : public nsISupports { 38 public: 39 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 40 NS_DECL_CYCLE_COLLECTION_CLASS(UnderlyingSourceAlgorithmsBase) 41 42 MOZ_CAN_RUN_SCRIPT virtual void StartCallback( 43 JSContext* aCx, ReadableStreamControllerBase& aController, 44 JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) = 0; 45 46 // A promise-returning algorithm that pulls data from the underlying byte 47 // source 48 MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> PullCallback( 49 JSContext* aCx, ReadableStreamControllerBase& aController, 50 ErrorResult& aRv) = 0; 51 52 // A promise-returning algorithm, taking one argument (the cancel reason), 53 // which communicates a requested cancelation to the underlying byte source 54 MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> CancelCallback( 55 JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason, 56 ErrorResult& aRv) = 0; 57 58 // Implement this when you need to release underlying resources immediately 59 // from closed(canceled)/errored streams, without waiting for GC. 60 virtual void ReleaseObjects() {} 61 62 // Can be used to read chunks directly via nsIInputStream to skip JS-related 63 // overhead, if this readable stream is a wrapper of a native stream. 64 // Currently used by Fetch helper functions e.g. new Response(stream).text() 65 virtual nsIInputStream* MaybeGetInputStreamIfUnread() { return nullptr; } 66 67 // https://streams.spec.whatwg.org/#other-specs-rs-create 68 // By "native" we mean "instances initialized via the above set up or set up 69 // with byte reading support algorithms (not, e.g., on web-developer-created 70 // instances)" 71 virtual bool IsNative() { return true; } 72 73 protected: 74 virtual ~UnderlyingSourceAlgorithmsBase() = default; 75 }; 76 77 class UnderlyingSourceAlgorithms final : public UnderlyingSourceAlgorithmsBase { 78 public: 79 NS_DECL_ISUPPORTS_INHERITED 80 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED( 81 UnderlyingSourceAlgorithms, UnderlyingSourceAlgorithmsBase) 82 83 UnderlyingSourceAlgorithms(nsIGlobalObject* aGlobal, 84 JS::Handle<JSObject*> aUnderlyingSource, 85 UnderlyingSource& aUnderlyingSourceDict) 86 : mGlobal(aGlobal), mUnderlyingSource(aUnderlyingSource) { 87 // Step 6. (implicit Step 2.) 88 if (aUnderlyingSourceDict.mStart.WasPassed()) { 89 mStartCallback = aUnderlyingSourceDict.mStart.Value(); 90 } 91 92 // Step 7. (implicit Step 3.) 93 if (aUnderlyingSourceDict.mPull.WasPassed()) { 94 mPullCallback = aUnderlyingSourceDict.mPull.Value(); 95 } 96 97 // Step 8. (implicit Step 4.) 98 if (aUnderlyingSourceDict.mCancel.WasPassed()) { 99 mCancelCallback = aUnderlyingSourceDict.mCancel.Value(); 100 } 101 102 mozilla::HoldJSObjects(this); 103 }; 104 105 MOZ_CAN_RUN_SCRIPT void StartCallback( 106 JSContext* aCx, ReadableStreamControllerBase& aController, 107 JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) override; 108 109 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PullCallback( 110 JSContext* aCx, ReadableStreamControllerBase& aController, 111 ErrorResult& aRv) override; 112 113 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CancelCallback( 114 JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason, 115 ErrorResult& aRv) override; 116 117 bool IsNative() override { return false; } 118 119 protected: 120 ~UnderlyingSourceAlgorithms() override { mozilla::DropJSObjects(this); }; 121 122 private: 123 // Virtually const, but are cycle collected 124 nsCOMPtr<nsIGlobalObject> mGlobal; 125 JS::Heap<JSObject*> mUnderlyingSource; 126 MOZ_KNOWN_LIVE RefPtr<UnderlyingSourceStartCallback> mStartCallback; 127 MOZ_KNOWN_LIVE RefPtr<UnderlyingSourcePullCallback> mPullCallback; 128 MOZ_KNOWN_LIVE RefPtr<UnderlyingSourceCancelCallback> mCancelCallback; 129 }; 130 131 // https://streams.spec.whatwg.org/#readablestream-set-up 132 // https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support 133 // Wrappers defined by the "Set up" methods in the spec. This helps you just 134 // return nullptr when an error occurred as this wrapper converts it to a 135 // rejected promise. 136 // Note that StartCallback is only for JS consumers to access 137 // the controller, and thus is no-op here since native consumers can call 138 // `EnqueueNative()` etc. without direct controller access. 139 class UnderlyingSourceAlgorithmsWrapper 140 : public UnderlyingSourceAlgorithmsBase { 141 void StartCallback(JSContext*, ReadableStreamControllerBase&, 142 JS::MutableHandle<JS::Value> aRetVal, ErrorResult&) final; 143 144 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PullCallback( 145 JSContext* aCx, ReadableStreamControllerBase& aController, 146 ErrorResult& aRv) final; 147 148 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CancelCallback( 149 JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason, 150 ErrorResult& aRv) final; 151 152 MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> PullCallbackImpl( 153 JSContext* aCx, ReadableStreamControllerBase& aController, 154 ErrorResult& aRv) { 155 // pullAlgorithm is optional, return null by default 156 return nullptr; 157 } 158 159 MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> CancelCallbackImpl( 160 JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason, 161 ErrorResult& aRv) { 162 // cancelAlgorithm is optional, return null by default 163 return nullptr; 164 } 165 }; 166 167 class InputToReadableStreamAlgorithms; 168 169 // This class exists to isolate InputToReadableStreamAlgorithms from the 170 // nsIAsyncInputStream. If we call AsyncWait(this,...), it holds a 171 // reference to 'this' which can't be cc'd, and we can leak the stream, 172 // causing a Worker to assert with globalScopeAlive. By isolating 173 // ourselves from the inputstream, we can safely be CC'd if needed and 174 // will inform the inputstream to shut down. 175 class InputStreamHolder final : public nsIInputStreamCallback, 176 public GlobalTeardownObserver { 177 public: 178 NS_DECL_THREADSAFE_ISUPPORTS 179 NS_DECL_NSIINPUTSTREAMCALLBACK 180 181 InputStreamHolder(nsIGlobalObject* aGlobal, 182 InputToReadableStreamAlgorithms* aCallback, 183 nsIAsyncInputStream* aInput); 184 185 void Init(JSContext* aCx); 186 187 void DisconnectFromOwner() override; 188 189 // Used by global teardown 190 void Shutdown(); 191 192 // These just proxy the calls to the nsIAsyncInputStream 193 nsresult AsyncWait(uint32_t aFlags, uint32_t aRequestedCount, 194 nsIEventTarget* aEventTarget); 195 nsresult Available(uint64_t* aSize) { return mInput->Available(aSize); } 196 nsresult Read(char* aBuffer, uint32_t aLength, uint32_t* aWritten) { 197 return mInput->Read(aBuffer, aLength, aWritten); 198 } 199 nsresult CloseWithStatus(nsresult aStatus) { 200 return mInput->CloseWithStatus(aStatus); 201 } 202 203 nsIAsyncInputStream* GetInputStream() { return mInput; } 204 205 private: 206 ~InputStreamHolder(); 207 208 // WeakPtr to avoid cycles 209 WeakPtr<InputToReadableStreamAlgorithms> mCallback; 210 // To ensure the worker sticks around 211 RefPtr<StrongWorkerRef> mAsyncWaitWorkerRef; 212 RefPtr<StrongWorkerRef> mWorkerRef; 213 nsCOMPtr<nsIAsyncInputStream> mInput; 214 215 // To ensure the underlying source sticks around during an ongoing read 216 // operation. mAlgorithms is not cycle collected on purpose, and this holder 217 // is responsible to keep the underlying source algorithms until 218 // nsIAsyncInputStream responds. 219 // 220 // This is done because otherwise the whole stream objects may be cycle 221 // collected, including the promises created from read(), as our JS engine may 222 // throw unsettled promises away for optimization. See bug 1849860. 223 RefPtr<InputToReadableStreamAlgorithms> mAsyncWaitAlgorithms; 224 }; 225 226 // Using this class means you are also passing the lifetime control of your 227 // nsIAsyncInputStream, as it will be closed when this class tears down. 228 class InputToReadableStreamAlgorithms final 229 : public UnderlyingSourceAlgorithmsWrapper, 230 public nsIInputStreamCallback, 231 public SupportsWeakPtr { 232 NS_DECL_ISUPPORTS_INHERITED 233 NS_DECL_NSIINPUTSTREAMCALLBACK 234 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InputToReadableStreamAlgorithms, 235 UnderlyingSourceAlgorithmsWrapper) 236 237 InputToReadableStreamAlgorithms(JSContext* aCx, nsIAsyncInputStream* aInput, 238 ReadableStream* aStream); 239 240 // Streams algorithms 241 242 already_AddRefed<Promise> PullCallbackImpl( 243 JSContext* aCx, ReadableStreamControllerBase& aController, 244 ErrorResult& aRv) override; 245 246 void ReleaseObjects() override; 247 248 nsIInputStream* MaybeGetInputStreamIfUnread() override; 249 250 private: 251 ~InputToReadableStreamAlgorithms() { 252 if (mInput) { 253 mInput->Shutdown(); 254 } 255 } 256 257 MOZ_CAN_RUN_SCRIPT_BOUNDARY void CloseAndReleaseObjects( 258 JSContext* aCx, ReadableStream* aStream); 259 260 void WriteIntoReadRequestBuffer(JSContext* aCx, ReadableStream* aStream, 261 JS::Handle<JSObject*> aBuffer, 262 uint32_t aLength, uint32_t* aByteWritten, 263 ErrorResult& aRv); 264 265 // https://streams.spec.whatwg.org/#readablestream-pull-from-bytes 266 // (Uses InputStreamHolder for the "byte sequence" in the spec) 267 MOZ_CAN_RUN_SCRIPT void PullFromInputStream(JSContext* aCx, 268 uint64_t aAvailable, 269 ErrorResult& aRv); 270 271 void ErrorPropagation(JSContext* aCx, ReadableStream* aStream, 272 nsresult aError); 273 274 // Common methods 275 276 bool IsClosed() { return !mInput; } 277 278 nsCOMPtr<nsIEventTarget> mOwningEventTarget; 279 280 // This promise is created by PullCallback and resolved when 281 // OnInputStreamReady succeeds. No need to try hard to settle it though, see 282 // also ReleaseObjects() for the reason. 283 RefPtr<Promise> mPullPromise; 284 285 RefPtr<InputStreamHolder> mInput; 286 287 // mStream never changes after construction and before CC 288 MOZ_KNOWN_LIVE RefPtr<ReadableStream> mStream; 289 }; 290 291 class NonAsyncInputToReadableStreamAlgorithms 292 : public UnderlyingSourceAlgorithmsWrapper { 293 public: 294 NS_DECL_ISUPPORTS_INHERITED 295 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( 296 NonAsyncInputToReadableStreamAlgorithms, 297 UnderlyingSourceAlgorithmsWrapper) 298 299 explicit NonAsyncInputToReadableStreamAlgorithms(nsIInputStream& aInput) 300 : mInput(&aInput) {} 301 302 already_AddRefed<Promise> PullCallbackImpl( 303 JSContext* aCx, ReadableStreamControllerBase& aController, 304 ErrorResult& aRv) override; 305 306 void ReleaseObjects() override { 307 if (RefPtr<InputToReadableStreamAlgorithms> algorithms = 308 mAsyncAlgorithms.forget()) { 309 algorithms->ReleaseObjects(); 310 } 311 if (nsCOMPtr<nsIInputStream> input = mInput.forget()) { 312 input->Close(); 313 } 314 } 315 316 nsIInputStream* MaybeGetInputStreamIfUnread() override { 317 MOZ_ASSERT(mInput, "Should be only called on non-disturbed streams"); 318 return mInput; 319 } 320 321 private: 322 ~NonAsyncInputToReadableStreamAlgorithms() = default; 323 324 nsCOMPtr<nsIInputStream> mInput; 325 RefPtr<InputToReadableStreamAlgorithms> mAsyncAlgorithms; 326 }; 327 328 } // namespace mozilla::dom 329 330 #endif