1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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_Fetch_h 8 #define mozilla_dom_Fetch_h 9 10 #include "nsAutoPtr.h" 11 #include "nsIStreamLoader.h" 12 13 #include "nsCOMPtr.h" 14 #include "nsError.h" 15 #include "nsProxyRelease.h" 16 #include "nsString.h" 17 18 #include "mozilla/DebugOnly.h" 19 #include "mozilla/ErrorResult.h" 20 #include "mozilla/dom/AbortSignal.h" 21 #include "mozilla/dom/Promise.h" 22 #include "mozilla/dom/FetchStreamReader.h" 23 #include "mozilla/dom/RequestBinding.h" 24 25 class nsIGlobalObject; 26 class nsIEventTarget; 27 28 namespace mozilla { 29 namespace dom { 30 31 class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString; 32 class 33 BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString; 34 class BlobImpl; 35 class InternalRequest; 36 class 37 OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString; 38 struct ReadableStream; 39 class RequestOrUSVString; 40 class WorkerPrivate; 41 42 enum class CallerType : uint32_t; 43 44 already_AddRefed<Promise> FetchRequest(nsIGlobalObject* aGlobal, 45 const RequestOrUSVString& aInput, 46 const RequestInit& aInit, 47 CallerType aCallerType, 48 ErrorResult& aRv); 49 50 nsresult UpdateRequestReferrer(nsIGlobalObject* aGlobal, 51 InternalRequest* aRequest); 52 53 namespace fetch { 54 typedef BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString 55 BodyInit; 56 typedef BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString 57 ResponseBodyInit; 58 typedef OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString 59 OwningBodyInit; 60 }; // namespace fetch 61 62 /* 63 * Creates an nsIInputStream based on the fetch specifications 'extract a byte 64 * stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract. 65 * Stores content type in out param aContentType. 66 */ 67 nsresult ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit, 68 nsIInputStream** aStream, 69 nsCString& aContentType, 70 uint64_t& aContentLength); 71 72 /* 73 * Non-owning version. 74 */ 75 nsresult ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit, 76 nsIInputStream** aStream, 77 nsCString& aContentType, 78 uint64_t& aContentLength); 79 80 /* 81 * Non-owning version. This method should go away when BodyInit will contain 82 * ReadableStream. 83 */ 84 nsresult ExtractByteStreamFromBody(const fetch::ResponseBodyInit& aBodyInit, 85 nsIInputStream** aStream, 86 nsCString& aContentType, 87 uint64_t& aContentLength); 88 89 template <class Derived> 90 class FetchBodyConsumer; 91 92 enum FetchConsumeType { 93 CONSUME_ARRAYBUFFER, 94 CONSUME_BLOB, 95 CONSUME_FORMDATA, 96 CONSUME_JSON, 97 CONSUME_TEXT, 98 }; 99 100 class FetchStreamHolder { 101 public: 102 NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING 103 104 virtual void NullifyStream() = 0; 105 106 virtual JSObject* ReadableStreamBody() = 0; 107 }; 108 109 /* 110 * FetchBody's body consumption uses nsIInputStreamPump to read from the 111 * underlying stream to a block of memory, which is then adopted by 112 * ContinueConsumeBody() and converted to the right type based on the JS 113 * function called. 114 * 115 * Use of the nsIInputStreamPump complicates things on the worker thread. 116 * The solution used here is similar to WebSockets. 117 * The difference is that we are only interested in completion and not data 118 * events, and nsIInputStreamPump can only deliver completion on the main 119 * thread. 120 * 121 * Before starting the pump on the main thread, we addref the FetchBody to keep 122 * it alive. Then we add a feature, to track the status of the worker. 123 * 124 * ContinueConsumeBody() is the function that cleans things up in both success 125 * and error conditions and so all callers call it with the appropriate status. 126 * 127 * Once the read is initiated on the main thread there are two possibilities. 128 * 129 * 1) Pump finishes before worker has finished Running. 130 * In this case we adopt the data and dispatch a runnable to the worker, 131 * which derefs FetchBody and removes the feature and resolves the Promise. 132 * 133 * 2) Pump still working while worker has stopped Running. 134 * The feature is Notify()ed and ContinueConsumeBody() is called with 135 * NS_BINDING_ABORTED. We first Cancel() the pump using a sync runnable to 136 * ensure that mFetchBody remains alive (since mConsumeBodyPump is strongly 137 * held by it) until pump->Cancel() is called. OnStreamComplete() will not 138 * do anything if the error code is NS_BINDING_ABORTED, so we don't have to 139 * worry about keeping anything alive. 140 * 141 * The pump is always released on the main thread. 142 */ 143 template <class Derived> 144 class FetchBody : public FetchStreamHolder, public AbortFollower { 145 public: 146 friend class FetchBodyConsumer<Derived>; 147 148 bool BodyUsed() const; 149 ArrayBuffer(JSContext * aCx,ErrorResult & aRv)150 already_AddRefed<Promise> ArrayBuffer(JSContext* aCx, ErrorResult& aRv) { 151 return ConsumeBody(aCx, CONSUME_ARRAYBUFFER, aRv); 152 } 153 Blob(JSContext * aCx,ErrorResult & aRv)154 already_AddRefed<Promise> Blob(JSContext* aCx, ErrorResult& aRv) { 155 return ConsumeBody(aCx, CONSUME_BLOB, aRv); 156 } 157 FormData(JSContext * aCx,ErrorResult & aRv)158 already_AddRefed<Promise> FormData(JSContext* aCx, ErrorResult& aRv) { 159 return ConsumeBody(aCx, CONSUME_FORMDATA, aRv); 160 } 161 Json(JSContext * aCx,ErrorResult & aRv)162 already_AddRefed<Promise> Json(JSContext* aCx, ErrorResult& aRv) { 163 return ConsumeBody(aCx, CONSUME_JSON, aRv); 164 } 165 Text(JSContext * aCx,ErrorResult & aRv)166 already_AddRefed<Promise> Text(JSContext* aCx, ErrorResult& aRv) { 167 return ConsumeBody(aCx, CONSUME_TEXT, aRv); 168 } 169 170 void GetBody(JSContext* aCx, JS::MutableHandle<JSObject*> aBodyOut, 171 ErrorResult& aRv); 172 173 // If the body contains a ReadableStream body object, this method produces a 174 // tee() of it. 175 void MaybeTeeReadableStreamBody(JSContext* aCx, 176 JS::MutableHandle<JSObject*> aBodyOut, 177 FetchStreamReader** aStreamReader, 178 nsIInputStream** aInputStream, 179 ErrorResult& aRv); 180 181 // Utility public methods accessed by various runnables. 182 183 // This method _must_ be called in order to set the body as used. If the body 184 // is a ReadableStream, this method will start reading the stream. 185 // More in details, this method does: 186 // 1) It uses an internal flag to track if the body is used. This is tracked 187 // separately from the ReadableStream disturbed state due to purely native 188 // streams. 189 // 2) If there is a ReadableStream reflector for the native stream it is 190 // Locked. 191 // 3) If there is a JS ReadableStream then we begin pumping it into the native 192 // body stream. This effectively locks and disturbs the stream. 193 // 194 // Note that JSContext is used only if there is a ReadableStream (this can 195 // happen because the body is a ReadableStream or because attribute body has 196 // already been used by content). If something goes wrong using 197 // ReadableStream, errors will be reported via ErrorResult and not as JS 198 // exceptions in JSContext. This is done in order to have a centralized error 199 // reporting way. 200 // 201 // Exceptions generated when reading from the ReadableStream are directly sent 202 // to the Console. 203 void SetBodyUsed(JSContext* aCx, ErrorResult& aRv); 204 MimeType()205 const nsCString& MimeType() const { return mMimeType; } 206 207 // FetchStreamHolder NullifyStream()208 void NullifyStream() override { 209 mReadableStreamBody = nullptr; 210 mReadableStreamReader = nullptr; 211 mFetchStreamReader = nullptr; 212 } 213 ReadableStreamBody()214 JSObject* ReadableStreamBody() override { 215 MOZ_ASSERT(mReadableStreamBody); 216 return mReadableStreamBody; 217 } 218 219 virtual AbortSignal* GetSignal() const = 0; 220 221 // AbortFollower 222 void Abort() override; 223 224 protected: 225 nsCOMPtr<nsIGlobalObject> mOwner; 226 227 // Always set whenever the FetchBody is created on the worker thread. 228 WorkerPrivate* mWorkerPrivate; 229 230 // This is the ReadableStream exposed to content. It's underlying source is a 231 // FetchStream object. 232 JS::Heap<JSObject*> mReadableStreamBody; 233 234 // This is the Reader used to retrieve data from the body. 235 JS::Heap<JSObject*> mReadableStreamReader; 236 RefPtr<FetchStreamReader> mFetchStreamReader; 237 238 explicit FetchBody(nsIGlobalObject* aOwner); 239 240 virtual ~FetchBody(); 241 242 void SetMimeType(); 243 244 void SetReadableStreamBody(JSContext* aCx, JSObject* aBody); 245 246 private: DerivedClass()247 Derived* DerivedClass() const { 248 return static_cast<Derived*>(const_cast<FetchBody*>(this)); 249 } 250 251 already_AddRefed<Promise> ConsumeBody(JSContext* aCx, FetchConsumeType aType, 252 ErrorResult& aRv); 253 254 void LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv); 255 IsOnTargetThread()256 bool IsOnTargetThread() { return NS_IsMainThread() == !mWorkerPrivate; } 257 AssertIsOnTargetThread()258 void AssertIsOnTargetThread() { MOZ_ASSERT(IsOnTargetThread()); } 259 260 // Only ever set once, always on target thread. 261 bool mBodyUsed; 262 nsCString mMimeType; 263 264 // The main-thread event target for runnable dispatching. 265 nsCOMPtr<nsIEventTarget> mMainThreadEventTarget; 266 }; 267 268 } // namespace dom 269 } // namespace mozilla 270 271 #endif // mozilla_dom_Fetch_h 272