Bug 1639230: Remove vendor prefix for printing with webdriver r=jgraham,webdriver...
[gecko.git] / dom / worklet / Worklet.cpp
blob78f80371d05f007e23ba9a131038f0cb50f998aa
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"
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"
29 namespace mozilla {
30 namespace dom {
32 class ExecutionRunnable final : public Runnable {
33 public:
34 ExecutionRunnable(WorkletFetchHandler* aHandler, WorkletImpl* aWorkletImpl,
35 JS::UniqueTwoByteChars aScriptBuffer, size_t aScriptLength)
36 : Runnable("Worklet::ExecutionRunnable"),
37 mHandler(aHandler),
38 mWorkletImpl(aWorkletImpl),
39 mScriptBuffer(std::move(aScriptBuffer)),
40 mScriptLength(aScriptLength),
41 mParentRuntime(
42 JS_GetParentRuntime(CycleCollectedJSContext::Get()->Context())),
43 mResult(NS_ERROR_FAILURE) {
44 MOZ_ASSERT(NS_IsMainThread());
45 MOZ_ASSERT(mParentRuntime);
48 NS_IMETHOD
49 Run() override;
51 private:
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;
61 size_t mScriptLength;
62 JSRuntime* mParentRuntime;
63 nsresult mResult;
66 // ---------------------------------------------------------------------------
67 // WorkletFetchHandler
69 class WorkletFetchHandler final : public PromiseNativeHandler,
70 public nsIStreamLoaderObserver {
71 public:
72 NS_DECL_THREADSAFE_ISUPPORTS
74 static already_AddRefed<Promise> Fetch(Worklet* aWorklet, JSContext* aCx,
75 const nsAString& aModuleURL,
76 const WorkletOptions& aOptions,
77 ErrorResult& aRv) {
78 MOZ_ASSERT(aWorklet);
79 MOZ_ASSERT(NS_IsMainThread());
81 nsCOMPtr<nsIGlobalObject> global =
82 do_QueryInterface(aWorklet->GetParentObject());
83 MOZ_ASSERT(global);
85 RefPtr<Promise> promise = Promise::Create(global, aRv);
86 if (NS_WARN_IF(aRv.Failed())) {
87 return nullptr;
90 nsCOMPtr<nsPIDOMWindowInner> window = aWorklet->GetParentObject();
91 MOZ_ASSERT(window);
93 nsCOMPtr<Document> doc;
94 doc = window->GetExtantDoc();
95 if (!doc) {
96 promise->MaybeReject(NS_ERROR_FAILURE);
97 return promise.forget();
100 nsCOMPtr<nsIURI> resolvedURI;
101 nsresult rv = NS_NewURI(getter_AddRefs(resolvedURI), aModuleURL, nullptr,
102 doc->GetBaseURI());
103 if (NS_WARN_IF(NS_FAILED(rv))) {
104 promise->MaybeReject(rv);
105 return promise.forget();
108 nsAutoCString spec;
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);
118 if (handler) {
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);
132 if (aRv.Failed()) {
133 return nullptr;
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.
146 return nullptr;
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);
163 return;
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);
170 return;
173 if (!response->Ok()) {
174 RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
175 return;
178 nsCOMPtr<nsIInputStream> inputStream;
179 response->GetBody(getter_AddRefs(inputStream));
180 if (!inputStream) {
181 RejectPromises(NS_ERROR_DOM_NETWORK_ERR);
182 return;
185 nsCOMPtr<nsIInputStreamPump> pump;
186 rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream.forget());
187 if (NS_WARN_IF(NS_FAILED(rv))) {
188 RejectPromises(rv);
189 return;
192 nsCOMPtr<nsIStreamLoader> loader;
193 rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
194 if (NS_WARN_IF(NS_FAILED(rv))) {
195 RejectPromises(rv);
196 return;
199 rv = pump->AsyncRead(loader, nullptr);
200 if (NS_WARN_IF(NS_FAILED(rv))) {
201 RejectPromises(rv);
202 return;
205 nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump);
206 if (rr) {
207 nsCOMPtr<nsIEventTarget> sts =
208 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
209 rv = rr->RetargetDeliveryTo(sts);
210 if (NS_FAILED(rv)) {
211 NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
216 NS_IMETHOD
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);
224 return NS_OK;
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))) {
233 RejectPromises(rv);
234 return NS_OK;
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);
243 return NS_OK;
246 return NS_OK;
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());
259 RejectPromises(aRv);
262 void ExecutionSucceeded() {
263 MOZ_ASSERT(NS_IsMainThread());
264 ResolvePromises();
267 private:
268 WorkletFetchHandler(Worklet* aWorklet, const nsACString& aURL,
269 Promise* aPromise)
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());
284 switch (mStatus) {
285 case ePending:
286 mPromises.AppendElement(aPromise);
287 return;
289 case eRejected:
290 MOZ_ASSERT(NS_FAILED(mErrorStatus));
291 aPromise->MaybeReject(mErrorStatus);
292 return;
294 case eResolved:
295 aPromise->MaybeResolveWithUndefined();
296 return;
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);
308 mPromises.Clear();
310 mStatus = eRejected;
311 mErrorStatus = aResult;
312 mWorklet = nullptr;
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();
322 mPromises.Clear();
324 mStatus = eResolved;
325 mWorklet = nullptr;
328 RefPtr<Worklet> mWorklet;
329 nsTArray<RefPtr<Promise>> mPromises;
331 enum { ePending, eRejected, eResolved } mStatus;
333 nsresult mErrorStatus;
335 nsCString mURL;
338 NS_IMPL_ISUPPORTS(WorkletFetchHandler, nsIStreamLoaderObserver)
340 NS_IMETHODIMP
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);
351 RunOnMainThread();
352 return NS_OK;
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)) {
365 return false;
367 JS::Rooted<JSObject*> module(aCx,
368 JS::CompileModule(aCx, compileOptions, buffer));
369 if (!module) {
370 return false;
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)) {
376 return false;
378 aModule.set(module);
379 return true;
382 void ExecutionRunnable::RunOnWorkletThread() {
383 WorkletThread* workletThread =
384 static_cast<WorkletThread*>(NS_GetCurrentThread());
385 workletThread->EnsureCycleCollectedJSContext(mParentRuntime);
387 WorkletGlobalScope* globalScope = mWorkletImpl->GetGlobalScope();
388 if (!globalScope) {
389 mResult = NS_ERROR_DOM_UNKNOWN_ERR;
390 return;
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;
399 return;
402 // https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script
403 // invokes
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);
409 // All done.
410 mResult = NS_OK;
413 void ExecutionRunnable::RunOnMainThread() {
414 MOZ_ASSERT(NS_IsMainThread());
416 if (NS_FAILED(mResult)) {
417 mHandler->ExecutionFailed(mResult);
418 return;
421 mHandler->ExecutionSucceeded();
424 // ---------------------------------------------------------------------------
425 // Worklet
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)
449 NS_INTERFACE_MAP_END
451 Worklet::Worklet(nsPIDOMWindowInner* aWindow, RefPtr<WorkletImpl> aImpl,
452 nsISupports* aOwnedObject)
453 : mWindow(aWindow), mOwnedObject(aOwnedObject), mImpl(std::move(aImpl)) {
454 MOZ_ASSERT(aWindow);
455 MOZ_ASSERT(mImpl);
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,
470 ErrorResult& aRv) {
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});
489 } // namespace dom
490 } // namespace mozilla