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 #include "Worklet.h"
8 #include "AudioWorkletGlobalScope.h"
9 #include "PaintWorkletGlobalScope.h"
10 
11 #include "mozilla/dom/WorkletBinding.h"
12 #include "mozilla/dom/BlobBinding.h"
13 #include "mozilla/dom/Fetch.h"
14 #include "mozilla/dom/PromiseNativeHandler.h"
15 #include "mozilla/dom/RegisterWorkletBindings.h"
16 #include "mozilla/dom/Response.h"
17 #include "mozilla/dom/ScriptSettings.h"
18 #include "mozilla/dom/ScriptLoader.h"
19 #include "nsIInputStreamPump.h"
20 #include "nsIThreadRetargetableRequest.h"
21 #include "nsNetUtil.h"
22 #include "xpcprivate.h"
23 
24 namespace mozilla {
25 namespace dom {
26 
27 // ---------------------------------------------------------------------------
28 // WorkletFetchHandler
29 
30 class WorkletFetchHandler : public PromiseNativeHandler,
31                             public nsIStreamLoaderObserver {
32  public:
33   NS_DECL_ISUPPORTS
34 
Fetch(Worklet * aWorklet,const nsAString & aModuleURL,CallerType aCallerType,ErrorResult & aRv)35   static already_AddRefed<Promise> Fetch(Worklet* aWorklet,
36                                          const nsAString& aModuleURL,
37                                          CallerType aCallerType,
38                                          ErrorResult& aRv) {
39     MOZ_ASSERT(aWorklet);
40 
41     nsCOMPtr<nsIGlobalObject> global =
42         do_QueryInterface(aWorklet->GetParentObject());
43     MOZ_ASSERT(global);
44 
45     RefPtr<Promise> promise = Promise::Create(global, aRv);
46     if (NS_WARN_IF(aRv.Failed())) {
47       return nullptr;
48     }
49 
50     nsCOMPtr<nsPIDOMWindowInner> window = aWorklet->GetParentObject();
51     MOZ_ASSERT(window);
52 
53     nsCOMPtr<nsIDocument> doc;
54     doc = window->GetExtantDoc();
55     if (!doc) {
56       promise->MaybeReject(NS_ERROR_FAILURE);
57       return promise.forget();
58     }
59 
60     nsCOMPtr<nsIURI> baseURI = doc->GetBaseURI();
61     nsCOMPtr<nsIURI> resolvedURI;
62     nsresult rv =
63         NS_NewURI(getter_AddRefs(resolvedURI), aModuleURL, nullptr, baseURI);
64     if (NS_WARN_IF(NS_FAILED(rv))) {
65       promise->MaybeReject(rv);
66       return promise.forget();
67     }
68 
69     nsAutoCString spec;
70     rv = resolvedURI->GetSpec(spec);
71     if (NS_WARN_IF(NS_FAILED(rv))) {
72       promise->MaybeReject(rv);
73       return promise.forget();
74     }
75 
76     // Maybe we already have an handler for this URI
77     {
78       WorkletFetchHandler* handler = aWorklet->GetImportFetchHandler(spec);
79       if (handler) {
80         handler->AddPromise(promise);
81         return promise.forget();
82       }
83     }
84 
85     RequestOrUSVString request;
86     request.SetAsUSVString().Rebind(aModuleURL.Data(), aModuleURL.Length());
87 
88     RequestInit init;
89 
90     RefPtr<Promise> fetchPromise =
91         FetchRequest(global, request, init, aCallerType, aRv);
92     if (NS_WARN_IF(aRv.Failed())) {
93       promise->MaybeReject(aRv);
94       return promise.forget();
95     }
96 
97     RefPtr<WorkletFetchHandler> handler =
98         new WorkletFetchHandler(aWorklet, aModuleURL, promise);
99     fetchPromise->AppendNativeHandler(handler);
100 
101     aWorklet->AddImportFetchHandler(spec, handler);
102     return promise.forget();
103   }
104 
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)105   virtual void ResolvedCallback(JSContext* aCx,
106                                 JS::Handle<JS::Value> aValue) override {
107     if (!aValue.isObject()) {
108       RejectPromises(NS_ERROR_FAILURE);
109       return;
110     }
111 
112     RefPtr<Response> response;
113     nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
114     if (NS_WARN_IF(NS_FAILED(rv))) {
115       RejectPromises(NS_ERROR_FAILURE);
116       return;
117     }
118 
119     if (!response->Ok()) {
120       RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
121       return;
122     }
123 
124     nsCOMPtr<nsIInputStream> inputStream;
125     response->GetBody(getter_AddRefs(inputStream));
126     if (!inputStream) {
127       RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
128       return;
129     }
130 
131     nsCOMPtr<nsIInputStreamPump> pump;
132     rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream.forget());
133     if (NS_WARN_IF(NS_FAILED(rv))) {
134       RejectPromises(rv);
135       return;
136     }
137 
138     nsCOMPtr<nsIStreamLoader> loader;
139     rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
140     if (NS_WARN_IF(NS_FAILED(rv))) {
141       RejectPromises(rv);
142       return;
143     }
144 
145     rv = pump->AsyncRead(loader, nullptr);
146     if (NS_WARN_IF(NS_FAILED(rv))) {
147       RejectPromises(rv);
148       return;
149     }
150 
151     nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump);
152     if (rr) {
153       nsCOMPtr<nsIEventTarget> sts =
154           do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
155       rv = rr->RetargetDeliveryTo(sts);
156       if (NS_FAILED(rv)) {
157         NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
158       }
159     }
160   }
161 
162   NS_IMETHOD
OnStreamComplete(nsIStreamLoader * aLoader,nsISupports * aContext,nsresult aStatus,uint32_t aStringLen,const uint8_t * aString)163   OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
164                    nsresult aStatus, uint32_t aStringLen,
165                    const uint8_t* aString) override {
166     MOZ_ASSERT(NS_IsMainThread());
167 
168     if (NS_FAILED(aStatus)) {
169       RejectPromises(aStatus);
170       return NS_OK;
171     }
172 
173     char16_t* scriptTextBuf;
174     size_t scriptTextLength;
175     nsresult rv = ScriptLoader::ConvertToUTF16(
176         nullptr, aString, aStringLen, NS_LITERAL_STRING("UTF-8"), nullptr,
177         scriptTextBuf, scriptTextLength);
178     if (NS_WARN_IF(NS_FAILED(rv))) {
179       RejectPromises(rv);
180       return NS_OK;
181     }
182 
183     // Moving the ownership of the buffer
184     JS::SourceBufferHolder buffer(scriptTextBuf, scriptTextLength,
185                                   JS::SourceBufferHolder::GiveOwnership);
186 
187     AutoJSAPI jsapi;
188     jsapi.Init();
189 
190     RefPtr<WorkletGlobalScope> globalScope =
191         mWorklet->GetOrCreateGlobalScope(jsapi.cx());
192     MOZ_ASSERT(globalScope);
193 
194     AutoEntryScript aes(globalScope, "Worklet");
195     JSContext* cx = aes.cx();
196 
197     JS::Rooted<JSObject*> globalObj(cx, globalScope->GetGlobalJSObject());
198 
199     (void)new XPCWrappedNativeScope(cx, globalObj);
200 
201     NS_ConvertUTF16toUTF8 url(mURL);
202 
203     JS::CompileOptions compileOptions(cx);
204     compileOptions.setIntroductionType("Worklet");
205     compileOptions.setFileAndLine(url.get(), 0);
206     compileOptions.setIsRunOnce(true);
207     compileOptions.setNoScriptRval(true);
208 
209     JSAutoCompartment comp(cx, globalObj);
210 
211     JS::Rooted<JS::Value> unused(cx);
212     if (!JS::Evaluate(cx, compileOptions, buffer, &unused)) {
213       ErrorResult error;
214       error.MightThrowJSException();
215       error.StealExceptionFromJSContext(cx);
216       RejectPromises(error.StealNSResult());
217       return NS_OK;
218     }
219 
220     // All done.
221     ResolvePromises();
222     return NS_OK;
223   }
224 
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)225   virtual void RejectedCallback(JSContext* aCx,
226                                 JS::Handle<JS::Value> aValue) override {
227     RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
228   }
229 
230  private:
WorkletFetchHandler(Worklet * aWorklet,const nsAString & aURL,Promise * aPromise)231   WorkletFetchHandler(Worklet* aWorklet, const nsAString& aURL,
232                       Promise* aPromise)
233       : mWorklet(aWorklet), mStatus(ePending), mErrorStatus(NS_OK), mURL(aURL) {
234     MOZ_ASSERT(aWorklet);
235     MOZ_ASSERT(aPromise);
236 
237     mPromises.AppendElement(aPromise);
238   }
239 
~WorkletFetchHandler()240   ~WorkletFetchHandler() {}
241 
AddPromise(Promise * aPromise)242   void AddPromise(Promise* aPromise) {
243     MOZ_ASSERT(aPromise);
244 
245     switch (mStatus) {
246       case ePending:
247         mPromises.AppendElement(aPromise);
248         return;
249 
250       case eRejected:
251         MOZ_ASSERT(NS_FAILED(mErrorStatus));
252         aPromise->MaybeReject(mErrorStatus);
253         return;
254 
255       case eResolved:
256         aPromise->MaybeResolveWithUndefined();
257         return;
258     }
259   }
260 
RejectPromises(nsresult aResult)261   void RejectPromises(nsresult aResult) {
262     MOZ_ASSERT(mStatus == ePending);
263     MOZ_ASSERT(NS_FAILED(aResult));
264 
265     for (uint32_t i = 0; i < mPromises.Length(); ++i) {
266       mPromises[i]->MaybeReject(aResult);
267     }
268     mPromises.Clear();
269 
270     mStatus = eRejected;
271     mErrorStatus = aResult;
272     mWorklet = nullptr;
273   }
274 
ResolvePromises()275   void ResolvePromises() {
276     MOZ_ASSERT(mStatus == ePending);
277 
278     for (uint32_t i = 0; i < mPromises.Length(); ++i) {
279       mPromises[i]->MaybeResolveWithUndefined();
280     }
281     mPromises.Clear();
282 
283     mStatus = eResolved;
284     mWorklet = nullptr;
285   }
286 
287   RefPtr<Worklet> mWorklet;
288   nsTArray<RefPtr<Promise>> mPromises;
289 
290   enum { ePending, eRejected, eResolved } mStatus;
291 
292   nsresult mErrorStatus;
293 
294   nsString mURL;
295 };
296 
NS_IMPL_ISUPPORTS(WorkletFetchHandler,nsIStreamLoaderObserver)297 NS_IMPL_ISUPPORTS(WorkletFetchHandler, nsIStreamLoaderObserver)
298 
299 // ---------------------------------------------------------------------------
300 // Worklet
301 
302 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Worklet, mWindow, mScope)
303 NS_IMPL_CYCLE_COLLECTING_ADDREF(Worklet)
304 NS_IMPL_CYCLE_COLLECTING_RELEASE(Worklet)
305 
306 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Worklet)
307   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
308   NS_INTERFACE_MAP_ENTRY(nsISupports)
309 NS_INTERFACE_MAP_END
310 
311 Worklet::Worklet(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
312                  WorkletType aWorkletType)
313     : mWindow(aWindow), mPrincipal(aPrincipal), mWorkletType(aWorkletType) {
314   MOZ_ASSERT(aWindow);
315   MOZ_ASSERT(aPrincipal);
316 
317 #ifdef RELEASE_OR_BETA
318   MOZ_CRASH("This code should not go to release/beta yet!");
319 #endif
320 }
321 
~Worklet()322 Worklet::~Worklet() {}
323 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)324 JSObject* Worklet::WrapObject(JSContext* aCx,
325                               JS::Handle<JSObject*> aGivenProto) {
326   return WorkletBinding::Wrap(aCx, this, aGivenProto);
327 }
328 
Import(const nsAString & aModuleURL,CallerType aCallerType,ErrorResult & aRv)329 already_AddRefed<Promise> Worklet::Import(const nsAString& aModuleURL,
330                                           CallerType aCallerType,
331                                           ErrorResult& aRv) {
332   return WorkletFetchHandler::Fetch(this, aModuleURL, aCallerType, aRv);
333 }
334 
GetOrCreateGlobalScope(JSContext * aCx)335 WorkletGlobalScope* Worklet::GetOrCreateGlobalScope(JSContext* aCx) {
336   if (!mScope) {
337     switch (mWorkletType) {
338       case eAudioWorklet:
339         mScope = new AudioWorkletGlobalScope(mWindow);
340         break;
341       case ePaintWorklet:
342         mScope = new PaintWorkletGlobalScope(mWindow);
343         break;
344     }
345 
346     JS::Rooted<JSObject*> global(aCx);
347     NS_ENSURE_TRUE(mScope->WrapGlobalObject(aCx, mPrincipal, &global), nullptr);
348 
349     JSAutoCompartment ac(aCx, global);
350 
351     // Init Web IDL bindings
352     if (!RegisterWorkletBindings(aCx, global)) {
353       mScope = nullptr;
354       return nullptr;
355     }
356 
357     JS_FireOnNewGlobalObject(aCx, global);
358   }
359 
360   return mScope;
361 }
362 
GetImportFetchHandler(const nsACString & aURI)363 WorkletFetchHandler* Worklet::GetImportFetchHandler(const nsACString& aURI) {
364   return mImportHandlers.GetWeak(aURI);
365 }
366 
AddImportFetchHandler(const nsACString & aURI,WorkletFetchHandler * aHandler)367 void Worklet::AddImportFetchHandler(const nsACString& aURI,
368                                     WorkletFetchHandler* aHandler) {
369   MOZ_ASSERT(aHandler);
370   MOZ_ASSERT(!mImportHandlers.GetWeak(aURI));
371 
372   mImportHandlers.Put(aURI, aHandler);
373 }
374 
375 }  // namespace dom
376 }  // namespace mozilla
377