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/. */
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"
30 class ExecutionRunnable final
: public Runnable
33 ExecutionRunnable(WorkletFetchHandler
* aHandler
, Worklet::WorkletType aType
,
34 JS::UniqueTwoByteChars aScriptBuffer
, size_t aScriptLength
,
35 const WorkletLoadInfo
& aWorkletLoadInfo
)
36 : Runnable("Worklet::ExecutionRunnable")
38 , mScriptBuffer(std::move(aScriptBuffer
))
39 , mScriptLength(aScriptLength
)
41 , mResult(NS_ERROR_FAILURE
)
43 MOZ_ASSERT(NS_IsMainThread());
56 RefPtr
<WorkletFetchHandler
> mHandler
;
57 JS::UniqueTwoByteChars mScriptBuffer
;
59 Worklet::WorkletType mWorkletType
;
63 // ---------------------------------------------------------------------------
64 // WorkletFetchHandler
66 class WorkletFetchHandler final
: public PromiseNativeHandler
67 , public nsIStreamLoaderObserver
70 NS_DECL_THREADSAFE_ISUPPORTS
72 static already_AddRefed
<Promise
>
73 Fetch(Worklet
* aWorklet
, const nsAString
& aModuleURL
, CallerType aCallerType
,
77 MOZ_ASSERT(NS_IsMainThread());
79 nsCOMPtr
<nsIGlobalObject
> global
=
80 do_QueryInterface(aWorklet
->GetParentObject());
83 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
84 if (NS_WARN_IF(aRv
.Failed())) {
88 nsCOMPtr
<nsPIDOMWindowInner
> window
= aWorklet
->GetParentObject();
91 nsCOMPtr
<nsIDocument
> doc
;
92 doc
= window
->GetExtantDoc();
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,
102 if (NS_WARN_IF(NS_FAILED(rv
))) {
103 promise
->MaybeReject(rv
);
104 return promise
.forget();
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
);
118 handler
->AddPromise(promise
);
119 return promise
.forget();
123 RequestOrUSVString request
;
124 request
.SetAsUSVString().Rebind(aModuleURL
.Data(), aModuleURL
.Length());
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();
144 ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) override
146 MOZ_ASSERT(NS_IsMainThread());
148 if (!aValue
.isObject()) {
149 RejectPromises(NS_ERROR_FAILURE
);
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
);
160 if (!response
->Ok()) {
161 RejectPromises(NS_ERROR_DOM_NETWORK_ERR
);
165 nsCOMPtr
<nsIInputStream
> inputStream
;
166 response
->GetBody(getter_AddRefs(inputStream
));
168 RejectPromises(NS_ERROR_DOM_NETWORK_ERR
);
172 nsCOMPtr
<nsIInputStreamPump
> pump
;
173 rv
= NS_NewInputStreamPump(getter_AddRefs(pump
), inputStream
.forget());
174 if (NS_WARN_IF(NS_FAILED(rv
))) {
179 nsCOMPtr
<nsIStreamLoader
> loader
;
180 rv
= NS_NewStreamLoader(getter_AddRefs(loader
), this);
181 if (NS_WARN_IF(NS_FAILED(rv
))) {
186 rv
= pump
->AsyncRead(loader
, nullptr);
187 if (NS_WARN_IF(NS_FAILED(rv
))) {
192 nsCOMPtr
<nsIThreadRetargetableRequest
> rr
= do_QueryInterface(pump
);
194 nsCOMPtr
<nsIEventTarget
> sts
=
195 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
196 rv
= rr
->RetargetDeliveryTo(sts
);
198 NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
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
);
215 JS::UniqueTwoByteChars scriptTextBuf
;
216 size_t scriptTextLength
;
218 ScriptLoader::ConvertToUTF16(nullptr, aString
, aStringLen
,
219 NS_LITERAL_STRING("UTF-8"), nullptr,
220 scriptTextBuf
, scriptTextLength
);
221 if (NS_WARN_IF(NS_FAILED(rv
))) {
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();
233 RejectPromises(NS_ERROR_FAILURE
);
237 if (NS_FAILED(thread
->DispatchRunnable(runnable
.forget()))) {
238 RejectPromises(NS_ERROR_FAILURE
);
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
258 ExecutionFailed(nsresult aRv
)
260 MOZ_ASSERT(NS_IsMainThread());
267 MOZ_ASSERT(NS_IsMainThread());
272 WorkletFetchHandler(Worklet
* aWorklet
, const nsAString
& aURL
,
276 , mErrorStatus(NS_OK
)
279 MOZ_ASSERT(aWorklet
);
280 MOZ_ASSERT(aPromise
);
281 MOZ_ASSERT(NS_IsMainThread());
283 mPromises
.AppendElement(aPromise
);
286 ~WorkletFetchHandler()
290 AddPromise(Promise
* aPromise
)
292 MOZ_ASSERT(aPromise
);
293 MOZ_ASSERT(NS_IsMainThread());
297 mPromises
.AppendElement(aPromise
);
301 MOZ_ASSERT(NS_FAILED(mErrorStatus
));
302 aPromise
->MaybeReject(mErrorStatus
);
306 aPromise
->MaybeResolveWithUndefined();
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
);
324 mErrorStatus
= aResult
;
331 MOZ_ASSERT(mStatus
== ePending
);
332 MOZ_ASSERT(NS_IsMainThread());
334 for (uint32_t i
= 0; i
< mPromises
.Length(); ++i
) {
335 mPromises
[i
]->MaybeResolveWithUndefined();
343 RefPtr
<Worklet
> mWorklet
;
344 nsTArray
<RefPtr
<Promise
>> mPromises
;
352 nsresult mErrorStatus
;
357 NS_IMPL_ISUPPORTS(WorkletFetchHandler
, nsIStreamLoaderObserver
)
360 ExecutionRunnable::Run()
362 if (WorkletThread::IsOnWorkletThread()) {
363 RunOnWorkletThread();
364 return NS_DispatchToMainThread(this);
367 MOZ_ASSERT(NS_IsMainThread());
373 ExecutionRunnable::RunOnWorkletThread()
375 WorkletThread::AssertIsOnWorkletThread();
377 WorkletThread
* workletThread
= WorkletThread::Get();
378 MOZ_ASSERT(workletThread
);
380 JSContext
* cx
= workletThread
->GetJSContext();
381 JSAutoRequest
areq(cx
);
386 RefPtr
<WorkletGlobalScope
> globalScope
=
387 Worklet::CreateGlobalScope(jsapi
.cx(), mWorkletType
);
388 MOZ_ASSERT(globalScope
);
390 AutoEntryScript
aes(globalScope
, "Worklet");
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
)) {
410 error
.MightThrowJSException();
411 error
.StealExceptionFromJSContext(cx
);
412 mResult
= error
.StealNSResult();
421 ExecutionRunnable::RunOnMainThread()
423 MOZ_ASSERT(NS_IsMainThread());
425 if (NS_FAILED(mResult
)) {
426 mHandler
->ExecutionFailed(mResult
);
430 mHandler
->ExecutionSucceeded();
433 // ---------------------------------------------------------------------------
436 WorkletLoadInfo::WorkletLoadInfo()
438 MOZ_ASSERT(NS_IsMainThread());
441 WorkletLoadInfo::~WorkletLoadInfo()
443 MOZ_ASSERT(NS_IsMainThread());
446 // ---------------------------------------------------------------------------
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
)
471 Worklet::Worklet(nsPIDOMWindowInner
* aWindow
, nsIPrincipal
* aPrincipal
,
472 WorkletType aWorkletType
)
474 , mWorkletType(aWorkletType
)
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!");
484 // Reset mWorkletLoadInfo and populate it.
486 memset(&mWorkletLoadInfo
, 0, sizeof(WorkletLoadInfo
));
488 mWorkletLoadInfo
.mInnerWindowID
= aWindow
->WindowID();
490 nsPIDOMWindowOuter
* outerWindow
= aWindow
->GetOuterWindow();
492 mWorkletLoadInfo
.mOuterWindowID
= outerWindow
->WindowID();
495 mWorkletLoadInfo
.mOriginAttributes
=
496 BasePrincipal::Cast(aPrincipal
)->OriginAttributesRef();
498 mWorkletLoadInfo
.mPrincipal
= aPrincipal
;
500 mWorkletLoadInfo
.mDumpEnabled
= DOMPrefs::DumpEnabled();
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
);
515 return Worklet_Binding::Wrap(aCx
, this, aGivenProto
);
519 already_AddRefed
<Promise
>
520 Worklet::Import(const nsAString
& aModuleURL
, CallerType aCallerType
,
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
) {
536 scope
= new AudioWorkletGlobalScope();
539 scope
= new PaintWorkletGlobalScope();
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
)) {
553 JS_FireOnNewGlobalObject(aCx
, global
);
555 return scope
.forget();
559 Worklet::GetImportFetchHandler(const nsACString
& aURI
)
561 MOZ_ASSERT(NS_IsMainThread());
562 return mImportHandlers
.GetWeak(aURI
);
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
);
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
;
590 Worklet::TerminateThread()
592 MOZ_ASSERT(NS_IsMainThread());
593 if (!mWorkletThread
) {
597 mWorkletThread
->Terminate();
598 mWorkletThread
= nullptr;
599 mWorkletLoadInfo
.mPrincipal
= nullptr;
603 } // mozilla namespace