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