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