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