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"
10 #include "mozilla/dom/WorkletBinding.h"
11 #include "mozilla/dom/WorkletGlobalScope.h"
12 #include "mozilla/dom/BlobBinding.h"
13 #include "mozilla/dom/Fetch.h"
14 #include "mozilla/dom/PromiseNativeHandler.h"
15 #include "mozilla/dom/Request.h"
16 #include "mozilla/dom/Response.h"
17 #include "mozilla/dom/ScriptSettings.h"
18 #include "mozilla/dom/ScriptLoader.h"
19 #include "mozilla/dom/WorkletImpl.h"
20 #include "js/Modules.h"
21 #include "js/SourceText.h"
22 #include "nsIInputStreamPump.h"
23 #include "nsIStreamLoader.h"
24 #include "nsIThreadRetargetableRequest.h"
25 #include "nsIInputStreamPump.h"
26 #include "nsNetUtil.h"
27 #include "xpcprivate.h"
32 class ExecutionRunnable final
: public Runnable
{
34 ExecutionRunnable(WorkletFetchHandler
* aHandler
, WorkletImpl
* aWorkletImpl
,
35 JS::UniqueTwoByteChars aScriptBuffer
, size_t aScriptLength
)
36 : Runnable("Worklet::ExecutionRunnable"),
38 mWorkletImpl(aWorkletImpl
),
39 mScriptBuffer(std::move(aScriptBuffer
)),
40 mScriptLength(aScriptLength
),
42 JS_GetParentRuntime(CycleCollectedJSContext::Get()->Context())),
43 mResult(NS_ERROR_FAILURE
) {
44 MOZ_ASSERT(NS_IsMainThread());
45 MOZ_ASSERT(mParentRuntime
);
52 void RunOnWorkletThread();
54 void RunOnMainThread();
56 bool ParseAndLinkModule(JSContext
* aCx
, JS::MutableHandle
<JSObject
*> aModule
);
58 RefPtr
<WorkletFetchHandler
> mHandler
;
59 RefPtr
<WorkletImpl
> mWorkletImpl
;
60 JS::UniqueTwoByteChars mScriptBuffer
;
62 JSRuntime
* mParentRuntime
;
66 // ---------------------------------------------------------------------------
67 // WorkletFetchHandler
69 class WorkletFetchHandler final
: public PromiseNativeHandler
,
70 public nsIStreamLoaderObserver
{
72 NS_DECL_THREADSAFE_ISUPPORTS
74 static already_AddRefed
<Promise
> Fetch(Worklet
* aWorklet
, JSContext
* aCx
,
75 const nsAString
& aModuleURL
,
76 const WorkletOptions
& aOptions
,
79 MOZ_ASSERT(NS_IsMainThread());
81 nsCOMPtr
<nsIGlobalObject
> global
=
82 do_QueryInterface(aWorklet
->GetParentObject());
85 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
86 if (NS_WARN_IF(aRv
.Failed())) {
90 nsCOMPtr
<nsPIDOMWindowInner
> window
= aWorklet
->GetParentObject();
93 nsCOMPtr
<Document
> doc
;
94 doc
= window
->GetExtantDoc();
96 promise
->MaybeReject(NS_ERROR_FAILURE
);
97 return promise
.forget();
100 nsCOMPtr
<nsIURI
> resolvedURI
;
101 nsresult rv
= NS_NewURI(getter_AddRefs(resolvedURI
), aModuleURL
, nullptr,
103 if (NS_WARN_IF(NS_FAILED(rv
))) {
104 promise
->MaybeReject(rv
);
105 return promise
.forget();
109 rv
= resolvedURI
->GetSpec(spec
);
110 if (NS_WARN_IF(NS_FAILED(rv
))) {
111 promise
->MaybeReject(rv
);
112 return promise
.forget();
115 // Maybe we already have an handler for this URI
117 WorkletFetchHandler
* handler
= aWorklet
->GetImportFetchHandler(spec
);
119 handler
->AddPromise(promise
);
120 return promise
.forget();
124 RequestOrUSVString requestInput
;
125 requestInput
.SetAsUSVString().ShareOrDependUpon(aModuleURL
);
127 RequestInit requestInit
;
128 requestInit
.mCredentials
.Construct(aOptions
.mCredentials
);
130 SafeRefPtr
<Request
> request
=
131 Request::Constructor(global
, aCx
, requestInput
, requestInit
, aRv
);
136 request
->OverrideContentPolicyType(aWorklet
->Impl()->ContentPolicyType());
138 RequestOrUSVString finalRequestInput
;
139 finalRequestInput
.SetAsRequest() = request
.unsafeGetRawPtr();
141 RefPtr
<Promise
> fetchPromise
= FetchRequest(
142 global
, finalRequestInput
, requestInit
, CallerType::System
, aRv
);
143 if (NS_WARN_IF(aRv
.Failed())) {
144 // OK to just return null, since caller will ignore return value
145 // anyway if aRv is a failure.
149 RefPtr
<WorkletFetchHandler
> handler
=
150 new WorkletFetchHandler(aWorklet
, spec
, promise
);
151 fetchPromise
->AppendNativeHandler(handler
);
153 aWorklet
->AddImportFetchHandler(spec
, handler
);
154 return promise
.forget();
157 virtual void ResolvedCallback(JSContext
* aCx
,
158 JS::Handle
<JS::Value
> aValue
) override
{
159 MOZ_ASSERT(NS_IsMainThread());
161 if (!aValue
.isObject()) {
162 RejectPromises(NS_ERROR_FAILURE
);
166 RefPtr
<Response
> response
;
167 nsresult rv
= UNWRAP_OBJECT(Response
, &aValue
.toObject(), response
);
168 if (NS_WARN_IF(NS_FAILED(rv
))) {
169 RejectPromises(NS_ERROR_FAILURE
);
173 if (!response
->Ok()) {
174 RejectPromises(NS_ERROR_DOM_NETWORK_ERR
);
178 nsCOMPtr
<nsIInputStream
> inputStream
;
179 response
->GetBody(getter_AddRefs(inputStream
));
181 RejectPromises(NS_ERROR_DOM_NETWORK_ERR
);
185 nsCOMPtr
<nsIInputStreamPump
> pump
;
186 rv
= NS_NewInputStreamPump(getter_AddRefs(pump
), inputStream
.forget());
187 if (NS_WARN_IF(NS_FAILED(rv
))) {
192 nsCOMPtr
<nsIStreamLoader
> loader
;
193 rv
= NS_NewStreamLoader(getter_AddRefs(loader
), this);
194 if (NS_WARN_IF(NS_FAILED(rv
))) {
199 rv
= pump
->AsyncRead(loader
, nullptr);
200 if (NS_WARN_IF(NS_FAILED(rv
))) {
205 nsCOMPtr
<nsIThreadRetargetableRequest
> rr
= do_QueryInterface(pump
);
207 nsCOMPtr
<nsIEventTarget
> sts
=
208 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
209 rv
= rr
->RetargetDeliveryTo(sts
);
211 NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
217 OnStreamComplete(nsIStreamLoader
* aLoader
, nsISupports
* aContext
,
218 nsresult aStatus
, uint32_t aStringLen
,
219 const uint8_t* aString
) override
{
220 MOZ_ASSERT(NS_IsMainThread());
222 if (NS_FAILED(aStatus
)) {
223 RejectPromises(aStatus
);
227 JS::UniqueTwoByteChars scriptTextBuf
;
228 size_t scriptTextLength
;
229 nsresult rv
= ScriptLoader::ConvertToUTF16(
230 nullptr, aString
, aStringLen
, NS_LITERAL_STRING("UTF-8"), nullptr,
231 scriptTextBuf
, scriptTextLength
);
232 if (NS_WARN_IF(NS_FAILED(rv
))) {
237 // Moving the ownership of the buffer
238 nsCOMPtr
<nsIRunnable
> runnable
= new ExecutionRunnable(
239 this, mWorklet
->mImpl
, std::move(scriptTextBuf
), scriptTextLength
);
241 if (NS_FAILED(mWorklet
->mImpl
->SendControlMessage(runnable
.forget()))) {
242 RejectPromises(NS_ERROR_FAILURE
);
249 virtual void RejectedCallback(JSContext
* aCx
,
250 JS::Handle
<JS::Value
> aValue
) override
{
251 MOZ_ASSERT(NS_IsMainThread());
252 RejectPromises(NS_ERROR_DOM_NETWORK_ERR
);
255 const nsCString
& URL() const { return mURL
; }
257 void ExecutionFailed(nsresult aRv
) {
258 MOZ_ASSERT(NS_IsMainThread());
262 void ExecutionSucceeded() {
263 MOZ_ASSERT(NS_IsMainThread());
268 WorkletFetchHandler(Worklet
* aWorklet
, const nsACString
& aURL
,
270 : mWorklet(aWorklet
), mStatus(ePending
), mErrorStatus(NS_OK
), mURL(aURL
) {
271 MOZ_ASSERT(aWorklet
);
272 MOZ_ASSERT(aPromise
);
273 MOZ_ASSERT(NS_IsMainThread());
275 mPromises
.AppendElement(aPromise
);
278 ~WorkletFetchHandler() = default;
280 void AddPromise(Promise
* aPromise
) {
281 MOZ_ASSERT(aPromise
);
282 MOZ_ASSERT(NS_IsMainThread());
286 mPromises
.AppendElement(aPromise
);
290 MOZ_ASSERT(NS_FAILED(mErrorStatus
));
291 aPromise
->MaybeReject(mErrorStatus
);
295 aPromise
->MaybeResolveWithUndefined();
300 void RejectPromises(nsresult aResult
) {
301 MOZ_ASSERT(mStatus
== ePending
);
302 MOZ_ASSERT(NS_FAILED(aResult
));
303 MOZ_ASSERT(NS_IsMainThread());
305 for (uint32_t i
= 0; i
< mPromises
.Length(); ++i
) {
306 mPromises
[i
]->MaybeReject(aResult
);
311 mErrorStatus
= aResult
;
315 void ResolvePromises() {
316 MOZ_ASSERT(mStatus
== ePending
);
317 MOZ_ASSERT(NS_IsMainThread());
319 for (uint32_t i
= 0; i
< mPromises
.Length(); ++i
) {
320 mPromises
[i
]->MaybeResolveWithUndefined();
328 RefPtr
<Worklet
> mWorklet
;
329 nsTArray
<RefPtr
<Promise
>> mPromises
;
331 enum { ePending
, eRejected
, eResolved
} mStatus
;
333 nsresult mErrorStatus
;
338 NS_IMPL_ISUPPORTS(WorkletFetchHandler
, nsIStreamLoaderObserver
)
341 ExecutionRunnable::Run() {
342 // WorkletThread::IsOnWorkletThread() cannot be used here because it depends
343 // on a WorkletJSContext having been created for this thread. That does not
344 // happen until the global scope is created the first time
345 // RunOnWorkletThread() is called.
346 if (!NS_IsMainThread()) {
347 RunOnWorkletThread();
348 return NS_DispatchToMainThread(this);
355 bool ExecutionRunnable::ParseAndLinkModule(
356 JSContext
* aCx
, JS::MutableHandle
<JSObject
*> aModule
) {
357 JS::CompileOptions
compileOptions(aCx
);
358 compileOptions
.setIntroductionType("Worklet");
359 compileOptions
.setFileAndLine(mHandler
->URL().get(), 1);
360 compileOptions
.setIsRunOnce(true);
361 compileOptions
.setNoScriptRval(true);
363 JS::SourceText
<char16_t
> buffer
;
364 if (!buffer
.init(aCx
, std::move(mScriptBuffer
), mScriptLength
)) {
367 JS::Rooted
<JSObject
*> module(aCx
,
368 JS::CompileModule(aCx
, compileOptions
, buffer
));
372 // Link() was previously named Instantiate().
373 // https://github.com/tc39/ecma262/pull/1312
374 // Any imports will fail here - bug 1572644.
375 if (!JS::ModuleInstantiate(aCx
, module
)) {
382 void ExecutionRunnable::RunOnWorkletThread() {
383 WorkletThread
* workletThread
=
384 static_cast<WorkletThread
*>(NS_GetCurrentThread());
385 workletThread
->EnsureCycleCollectedJSContext(mParentRuntime
);
387 WorkletGlobalScope
* globalScope
= mWorkletImpl
->GetGlobalScope();
389 mResult
= NS_ERROR_DOM_UNKNOWN_ERR
;
393 AutoEntryScript
aes(globalScope
, "Worklet");
394 JSContext
* cx
= aes
.cx();
396 JS::Rooted
<JSObject
*> module(cx
);
397 if (!ParseAndLinkModule(cx
, &module
)) {
398 mResult
= NS_ERROR_DOM_ABORT_ERR
;
402 // https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script
404 // https://html.spec.whatwg.org/multipage/webappapis.html#run-a-module-script
405 // without /rethrow errors/ and so unhandled exceptions do not cause the
406 // promise to be rejected.
407 JS::ModuleEvaluate(cx
, module
);
413 void ExecutionRunnable::RunOnMainThread() {
414 MOZ_ASSERT(NS_IsMainThread());
416 if (NS_FAILED(mResult
)) {
417 mHandler
->ExecutionFailed(mResult
);
421 mHandler
->ExecutionSucceeded();
424 // ---------------------------------------------------------------------------
427 NS_IMPL_CYCLE_COLLECTION_CLASS(Worklet
)
429 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Worklet
)
430 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow
)
431 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwnedObject
)
432 tmp
->mImpl
->NotifyWorkletFinished();
433 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
434 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
436 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Worklet
)
437 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow
)
438 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwnedObject
)
439 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
441 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Worklet
)
443 NS_IMPL_CYCLE_COLLECTING_ADDREF(Worklet
)
444 NS_IMPL_CYCLE_COLLECTING_RELEASE(Worklet
)
446 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Worklet
)
447 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
448 NS_INTERFACE_MAP_ENTRY(nsISupports
)
451 Worklet::Worklet(nsPIDOMWindowInner
* aWindow
, RefPtr
<WorkletImpl
> aImpl
,
452 nsISupports
* aOwnedObject
)
453 : mWindow(aWindow
), mOwnedObject(aOwnedObject
), mImpl(std::move(aImpl
)) {
456 MOZ_ASSERT(NS_IsMainThread());
459 Worklet::~Worklet() { mImpl
->NotifyWorkletFinished(); }
461 JSObject
* Worklet::WrapObject(JSContext
* aCx
,
462 JS::Handle
<JSObject
*> aGivenProto
) {
463 return mImpl
->WrapWorklet(aCx
, this, aGivenProto
);
466 already_AddRefed
<Promise
> Worklet::AddModule(JSContext
* aCx
,
467 const nsAString
& aModuleURL
,
468 const WorkletOptions
& aOptions
,
469 CallerType aCallerType
,
471 MOZ_ASSERT(NS_IsMainThread());
472 return WorkletFetchHandler::Fetch(this, aCx
, aModuleURL
, aOptions
, aRv
);
475 WorkletFetchHandler
* Worklet::GetImportFetchHandler(const nsACString
& aURI
) {
476 MOZ_ASSERT(NS_IsMainThread());
477 return mImportHandlers
.GetWeak(aURI
);
480 void Worklet::AddImportFetchHandler(const nsACString
& aURI
,
481 WorkletFetchHandler
* aHandler
) {
482 MOZ_ASSERT(aHandler
);
483 MOZ_ASSERT(!mImportHandlers
.GetWeak(aURI
));
484 MOZ_ASSERT(NS_IsMainThread());
486 mImportHandlers
.Put(aURI
, RefPtr
{aHandler
});
490 } // namespace mozilla