Bug 1265584 [wpt PR 11167] - [Gecko Bug 1265584] Move wptrunner marionette usage...
[gecko.git] / dom / worklet / Worklet.cpp
blobb2aef01e29e6924f0782bf0b8bccc4904a6ee588
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/. */
7 #include "Worklet.h"
8 #include "WorkletThread.h"
9 #include "AudioWorkletGlobalScope.h"
10 #include "PaintWorkletGlobalScope.h"
12 #include "mozilla/dom/WorkletBinding.h"
13 #include "mozilla/dom/AudioWorkletBinding.h"
14 #include "mozilla/dom/BlobBinding.h"
15 #include "mozilla/dom/DOMPrefs.h"
16 #include "mozilla/dom/Fetch.h"
17 #include "mozilla/dom/PromiseNativeHandler.h"
18 #include "mozilla/dom/RegisterWorkletBindings.h"
19 #include "mozilla/dom/Response.h"
20 #include "mozilla/dom/ScriptSettings.h"
21 #include "mozilla/dom/ScriptLoader.h"
22 #include "nsIInputStreamPump.h"
23 #include "nsIThreadRetargetableRequest.h"
24 #include "nsNetUtil.h"
25 #include "xpcprivate.h"
27 namespace mozilla {
28 namespace dom {
30 class ExecutionRunnable final : public Runnable
32 public:
33 ExecutionRunnable(WorkletFetchHandler* aHandler, Worklet::WorkletType aType,
34 JS::UniqueTwoByteChars aScriptBuffer, size_t aScriptLength,
35 const WorkletLoadInfo& aWorkletLoadInfo)
36 : Runnable("Worklet::ExecutionRunnable")
37 , mHandler(aHandler)
38 , mScriptBuffer(std::move(aScriptBuffer))
39 , mScriptLength(aScriptLength)
40 , mWorkletType(aType)
41 , mResult(NS_ERROR_FAILURE)
43 MOZ_ASSERT(NS_IsMainThread());
46 NS_IMETHOD
47 Run() override;
49 private:
50 void
51 RunOnWorkletThread();
53 void
54 RunOnMainThread();
56 RefPtr<WorkletFetchHandler> mHandler;
57 JS::UniqueTwoByteChars mScriptBuffer;
58 size_t mScriptLength;
59 Worklet::WorkletType mWorkletType;
60 nsresult mResult;
63 // ---------------------------------------------------------------------------
64 // WorkletFetchHandler
66 class WorkletFetchHandler final : public PromiseNativeHandler
67 , public nsIStreamLoaderObserver
69 public:
70 NS_DECL_THREADSAFE_ISUPPORTS
72 static already_AddRefed<Promise>
73 Fetch(Worklet* aWorklet, const nsAString& aModuleURL, CallerType aCallerType,
74 ErrorResult& aRv)
76 MOZ_ASSERT(aWorklet);
77 MOZ_ASSERT(NS_IsMainThread());
79 nsCOMPtr<nsIGlobalObject> global =
80 do_QueryInterface(aWorklet->GetParentObject());
81 MOZ_ASSERT(global);
83 RefPtr<Promise> promise = Promise::Create(global, aRv);
84 if (NS_WARN_IF(aRv.Failed())) {
85 return nullptr;
88 nsCOMPtr<nsPIDOMWindowInner> window = aWorklet->GetParentObject();
89 MOZ_ASSERT(window);
91 nsCOMPtr<nsIDocument> doc;
92 doc = window->GetExtantDoc();
93 if (!doc) {
94 promise->MaybeReject(NS_ERROR_FAILURE);
95 return promise.forget();
98 nsCOMPtr<nsIURI> baseURI = doc->GetBaseURI();
99 nsCOMPtr<nsIURI> resolvedURI;
100 nsresult rv = NS_NewURI(getter_AddRefs(resolvedURI), aModuleURL, nullptr,
101 baseURI);
102 if (NS_WARN_IF(NS_FAILED(rv))) {
103 promise->MaybeReject(rv);
104 return promise.forget();
107 nsAutoCString spec;
108 rv = resolvedURI->GetSpec(spec);
109 if (NS_WARN_IF(NS_FAILED(rv))) {
110 promise->MaybeReject(rv);
111 return promise.forget();
114 // Maybe we already have an handler for this URI
116 WorkletFetchHandler* handler = aWorklet->GetImportFetchHandler(spec);
117 if (handler) {
118 handler->AddPromise(promise);
119 return promise.forget();
123 RequestOrUSVString request;
124 request.SetAsUSVString().Rebind(aModuleURL.Data(), aModuleURL.Length());
126 RequestInit init;
128 RefPtr<Promise> fetchPromise =
129 FetchRequest(global, request, init, aCallerType, aRv);
130 if (NS_WARN_IF(aRv.Failed())) {
131 promise->MaybeReject(aRv);
132 return promise.forget();
135 RefPtr<WorkletFetchHandler> handler =
136 new WorkletFetchHandler(aWorklet, aModuleURL, promise);
137 fetchPromise->AppendNativeHandler(handler);
139 aWorklet->AddImportFetchHandler(spec, handler);
140 return promise.forget();
143 virtual void
144 ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
146 MOZ_ASSERT(NS_IsMainThread());
148 if (!aValue.isObject()) {
149 RejectPromises(NS_ERROR_FAILURE);
150 return;
153 RefPtr<Response> response;
154 nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
155 if (NS_WARN_IF(NS_FAILED(rv))) {
156 RejectPromises(NS_ERROR_FAILURE);
157 return;
160 if (!response->Ok()) {
161 RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
162 return;
165 nsCOMPtr<nsIInputStream> inputStream;
166 response->GetBody(getter_AddRefs(inputStream));
167 if (!inputStream) {
168 RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
169 return;
172 nsCOMPtr<nsIInputStreamPump> pump;
173 rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream.forget());
174 if (NS_WARN_IF(NS_FAILED(rv))) {
175 RejectPromises(rv);
176 return;
179 nsCOMPtr<nsIStreamLoader> loader;
180 rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
181 if (NS_WARN_IF(NS_FAILED(rv))) {
182 RejectPromises(rv);
183 return;
186 rv = pump->AsyncRead(loader, nullptr);
187 if (NS_WARN_IF(NS_FAILED(rv))) {
188 RejectPromises(rv);
189 return;
192 nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump);
193 if (rr) {
194 nsCOMPtr<nsIEventTarget> sts =
195 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
196 rv = rr->RetargetDeliveryTo(sts);
197 if (NS_FAILED(rv)) {
198 NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
203 NS_IMETHOD
204 OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
205 nsresult aStatus, uint32_t aStringLen,
206 const uint8_t* aString) override
208 MOZ_ASSERT(NS_IsMainThread());
210 if (NS_FAILED(aStatus)) {
211 RejectPromises(aStatus);
212 return NS_OK;
215 JS::UniqueTwoByteChars scriptTextBuf;
216 size_t scriptTextLength;
217 nsresult rv =
218 ScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen,
219 NS_LITERAL_STRING("UTF-8"), nullptr,
220 scriptTextBuf, scriptTextLength);
221 if (NS_WARN_IF(NS_FAILED(rv))) {
222 RejectPromises(rv);
223 return NS_OK;
226 // Moving the ownership of the buffer
227 nsCOMPtr<nsIRunnable> runnable =
228 new ExecutionRunnable(this, mWorklet->Type(), std::move(scriptTextBuf),
229 scriptTextLength, mWorklet->LoadInfo());
231 RefPtr<WorkletThread> thread = mWorklet->GetOrCreateThread();
232 if (!thread) {
233 RejectPromises(NS_ERROR_FAILURE);
234 return NS_OK;
237 if (NS_FAILED(thread->DispatchRunnable(runnable.forget()))) {
238 RejectPromises(NS_ERROR_FAILURE);
239 return NS_OK;
242 return NS_OK;
245 virtual void
246 RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
248 MOZ_ASSERT(NS_IsMainThread());
249 RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
252 const nsString& URL() const
254 return mURL;
257 void
258 ExecutionFailed(nsresult aRv)
260 MOZ_ASSERT(NS_IsMainThread());
261 RejectPromises(aRv);
264 void
265 ExecutionSucceeded()
267 MOZ_ASSERT(NS_IsMainThread());
268 ResolvePromises();
271 private:
272 WorkletFetchHandler(Worklet* aWorklet, const nsAString& aURL,
273 Promise* aPromise)
274 : mWorklet(aWorklet)
275 , mStatus(ePending)
276 , mErrorStatus(NS_OK)
277 , mURL(aURL)
279 MOZ_ASSERT(aWorklet);
280 MOZ_ASSERT(aPromise);
281 MOZ_ASSERT(NS_IsMainThread());
283 mPromises.AppendElement(aPromise);
286 ~WorkletFetchHandler()
289 void
290 AddPromise(Promise* aPromise)
292 MOZ_ASSERT(aPromise);
293 MOZ_ASSERT(NS_IsMainThread());
295 switch (mStatus) {
296 case ePending:
297 mPromises.AppendElement(aPromise);
298 return;
300 case eRejected:
301 MOZ_ASSERT(NS_FAILED(mErrorStatus));
302 aPromise->MaybeReject(mErrorStatus);
303 return;
305 case eResolved:
306 aPromise->MaybeResolveWithUndefined();
307 return;
311 void
312 RejectPromises(nsresult aResult)
314 MOZ_ASSERT(mStatus == ePending);
315 MOZ_ASSERT(NS_FAILED(aResult));
316 MOZ_ASSERT(NS_IsMainThread());
318 for (uint32_t i = 0; i < mPromises.Length(); ++i) {
319 mPromises[i]->MaybeReject(aResult);
321 mPromises.Clear();
323 mStatus = eRejected;
324 mErrorStatus = aResult;
325 mWorklet = nullptr;
328 void
329 ResolvePromises()
331 MOZ_ASSERT(mStatus == ePending);
332 MOZ_ASSERT(NS_IsMainThread());
334 for (uint32_t i = 0; i < mPromises.Length(); ++i) {
335 mPromises[i]->MaybeResolveWithUndefined();
337 mPromises.Clear();
339 mStatus = eResolved;
340 mWorklet = nullptr;
343 RefPtr<Worklet> mWorklet;
344 nsTArray<RefPtr<Promise>> mPromises;
346 enum {
347 ePending,
348 eRejected,
349 eResolved
350 } mStatus;
352 nsresult mErrorStatus;
354 nsString mURL;
357 NS_IMPL_ISUPPORTS(WorkletFetchHandler, nsIStreamLoaderObserver)
359 NS_IMETHODIMP
360 ExecutionRunnable::Run()
362 if (WorkletThread::IsOnWorkletThread()) {
363 RunOnWorkletThread();
364 return NS_DispatchToMainThread(this);
367 MOZ_ASSERT(NS_IsMainThread());
368 RunOnMainThread();
369 return NS_OK;
372 void
373 ExecutionRunnable::RunOnWorkletThread()
375 WorkletThread::AssertIsOnWorkletThread();
377 WorkletThread* workletThread = WorkletThread::Get();
378 MOZ_ASSERT(workletThread);
380 JSContext* cx = workletThread->GetJSContext();
381 JSAutoRequest areq(cx);
383 AutoJSAPI jsapi;
384 jsapi.Init();
386 RefPtr<WorkletGlobalScope> globalScope =
387 Worklet::CreateGlobalScope(jsapi.cx(), mWorkletType);
388 MOZ_ASSERT(globalScope);
390 AutoEntryScript aes(globalScope, "Worklet");
391 cx = aes.cx();
393 JS::Rooted<JSObject*> globalObj(cx, globalScope->GetGlobalJSObject());
395 NS_ConvertUTF16toUTF8 url(mHandler->URL());
397 JS::CompileOptions compileOptions(cx);
398 compileOptions.setIntroductionType("Worklet");
399 compileOptions.setFileAndLine(url.get(), 0);
400 compileOptions.setIsRunOnce(true);
401 compileOptions.setNoScriptRval(true);
403 JSAutoRealm ar(cx, globalObj);
405 JS::SourceBufferHolder buffer(mScriptBuffer.release(), mScriptLength,
406 JS::SourceBufferHolder::GiveOwnership);
407 JS::Rooted<JS::Value> unused(cx);
408 if (!JS::Evaluate(cx, compileOptions, buffer, &unused)) {
409 ErrorResult error;
410 error.MightThrowJSException();
411 error.StealExceptionFromJSContext(cx);
412 mResult = error.StealNSResult();
413 return;
416 // All done.
417 mResult = NS_OK;
420 void
421 ExecutionRunnable::RunOnMainThread()
423 MOZ_ASSERT(NS_IsMainThread());
425 if (NS_FAILED(mResult)) {
426 mHandler->ExecutionFailed(mResult);
427 return;
430 mHandler->ExecutionSucceeded();
433 // ---------------------------------------------------------------------------
434 // WorkletLoadInfo
436 WorkletLoadInfo::WorkletLoadInfo()
438 MOZ_ASSERT(NS_IsMainThread());
441 WorkletLoadInfo::~WorkletLoadInfo()
443 MOZ_ASSERT(NS_IsMainThread());
446 // ---------------------------------------------------------------------------
447 // Worklet
449 NS_IMPL_CYCLE_COLLECTION_CLASS(Worklet)
451 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Worklet)
452 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
453 tmp->TerminateThread();
454 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
455 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
457 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Worklet)
458 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
459 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
461 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Worklet)
463 NS_IMPL_CYCLE_COLLECTING_ADDREF(Worklet)
464 NS_IMPL_CYCLE_COLLECTING_RELEASE(Worklet)
466 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Worklet)
467 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
468 NS_INTERFACE_MAP_ENTRY(nsISupports)
469 NS_INTERFACE_MAP_END
471 Worklet::Worklet(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
472 WorkletType aWorkletType)
473 : mWindow(aWindow)
474 , mWorkletType(aWorkletType)
476 MOZ_ASSERT(aWindow);
477 MOZ_ASSERT(aPrincipal);
478 MOZ_ASSERT(NS_IsMainThread());
480 #ifdef RELEASE_OR_BETA
481 MOZ_CRASH("This code should not go to release/beta yet!");
482 #endif
484 // Reset mWorkletLoadInfo and populate it.
486 memset(&mWorkletLoadInfo, 0, sizeof(WorkletLoadInfo));
488 mWorkletLoadInfo.mInnerWindowID = aWindow->WindowID();
490 nsPIDOMWindowOuter* outerWindow = aWindow->GetOuterWindow();
491 if (outerWindow) {
492 mWorkletLoadInfo.mOuterWindowID = outerWindow->WindowID();
495 mWorkletLoadInfo.mOriginAttributes =
496 BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
498 mWorkletLoadInfo.mPrincipal = aPrincipal;
500 mWorkletLoadInfo.mDumpEnabled = DOMPrefs::DumpEnabled();
503 Worklet::~Worklet()
505 TerminateThread();
508 JSObject*
509 Worklet::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
511 MOZ_ASSERT(NS_IsMainThread());
512 if (mWorkletType == eAudioWorklet) {
513 return AudioWorklet_Binding::Wrap(aCx, this, aGivenProto);
514 } else {
515 return Worklet_Binding::Wrap(aCx, this, aGivenProto);
519 already_AddRefed<Promise>
520 Worklet::Import(const nsAString& aModuleURL, CallerType aCallerType,
521 ErrorResult& aRv)
523 MOZ_ASSERT(NS_IsMainThread());
524 return WorkletFetchHandler::Fetch(this, aModuleURL, aCallerType, aRv);
527 /* static */ already_AddRefed<WorkletGlobalScope>
528 Worklet::CreateGlobalScope(JSContext* aCx, WorkletType aWorkletType)
530 WorkletThread::AssertIsOnWorkletThread();
532 RefPtr<WorkletGlobalScope> scope;
534 switch (aWorkletType) {
535 case eAudioWorklet:
536 scope = new AudioWorkletGlobalScope();
537 break;
538 case ePaintWorklet:
539 scope = new PaintWorkletGlobalScope();
540 break;
543 JS::Rooted<JSObject*> global(aCx);
544 NS_ENSURE_TRUE(scope->WrapGlobalObject(aCx, &global), nullptr);
546 JSAutoRealm ar(aCx, global);
548 // Init Web IDL bindings
549 if (!RegisterWorkletBindings(aCx, global)) {
550 return nullptr;
553 JS_FireOnNewGlobalObject(aCx, global);
555 return scope.forget();
558 WorkletFetchHandler*
559 Worklet::GetImportFetchHandler(const nsACString& aURI)
561 MOZ_ASSERT(NS_IsMainThread());
562 return mImportHandlers.GetWeak(aURI);
565 void
566 Worklet::AddImportFetchHandler(const nsACString& aURI,
567 WorkletFetchHandler* aHandler)
569 MOZ_ASSERT(aHandler);
570 MOZ_ASSERT(!mImportHandlers.GetWeak(aURI));
571 MOZ_ASSERT(NS_IsMainThread());
573 mImportHandlers.Put(aURI, aHandler);
576 WorkletThread*
577 Worklet::GetOrCreateThread()
579 MOZ_ASSERT(NS_IsMainThread());
581 if (!mWorkletThread) {
582 // Thread creation. FIXME: this will change.
583 mWorkletThread = WorkletThread::Create(mWorkletLoadInfo);
586 return mWorkletThread;
589 void
590 Worklet::TerminateThread()
592 MOZ_ASSERT(NS_IsMainThread());
593 if (!mWorkletThread) {
594 return;
597 mWorkletThread->Terminate();
598 mWorkletThread = nullptr;
599 mWorkletLoadInfo.mPrincipal = nullptr;
602 } // dom namespace
603 } // mozilla namespace