mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-10-31 08:18:54 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			650 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			650 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | ||
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | ||
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | ||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | ||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | ||
| #include "WorkletFetchHandler.h"
 | ||
| 
 | ||
| #include "js/loader/ModuleLoadRequest.h"
 | ||
| #include "js/ContextOptions.h"
 | ||
| #include "mozilla/dom/Document.h"
 | ||
| #include "mozilla/dom/Fetch.h"
 | ||
| #include "mozilla/dom/Request.h"
 | ||
| #include "mozilla/dom/RequestBinding.h"
 | ||
| #include "mozilla/dom/Response.h"
 | ||
| #include "mozilla/dom/RootedDictionary.h"
 | ||
| #include "mozilla/dom/ScriptLoader.h"
 | ||
| #include "mozilla/dom/ScriptLoadHandler.h"  // ScriptDecoder
 | ||
| #include "mozilla/dom/Worklet.h"
 | ||
| #include "mozilla/dom/WorkletBinding.h"
 | ||
| #include "mozilla/dom/WorkletGlobalScope.h"
 | ||
| #include "mozilla/dom/WorkletImpl.h"
 | ||
| #include "mozilla/dom/WorkletThread.h"
 | ||
| #include "mozilla/dom/worklet/WorkletModuleLoader.h"
 | ||
| #include "mozilla/CycleCollectedJSContext.h"
 | ||
| #include "mozilla/ScopeExit.h"
 | ||
| #include "mozilla/TaskQueue.h"
 | ||
| #include "nsIInputStreamPump.h"
 | ||
| #include "nsIThreadRetargetableRequest.h"
 | ||
| #include "xpcpublic.h"
 | ||
| 
 | ||
| using JS::loader::ModuleLoadRequest;
 | ||
| using JS::loader::ParserMetadata;
 | ||
| using JS::loader::ScriptFetchOptions;
 | ||
| using mozilla::dom::loader::WorkletModuleLoader;
 | ||
| 
 | ||
| namespace mozilla::dom {
 | ||
| 
 | ||
| // A Runnable to call ModuleLoadRequest::StartModuleLoad on a worklet thread.
 | ||
| class StartModuleLoadRunnable final : public Runnable {
 | ||
|  public:
 | ||
|   StartModuleLoadRunnable(
 | ||
|       WorkletImpl* aWorkletImpl,
 | ||
|       const nsMainThreadPtrHandle<WorkletFetchHandler>& aHandlerRef,
 | ||
|       nsCOMPtr<nsIURI> aURI, nsIURI* aReferrer,
 | ||
|       const nsTArray<nsString>& aLocalizedStrs)
 | ||
|       : Runnable("Worklet::StartModuleLoadRunnable"),
 | ||
|         mWorkletImpl(aWorkletImpl),
 | ||
|         mHandlerRef(aHandlerRef),
 | ||
|         mURI(std::move(aURI)),
 | ||
|         mReferrer(aReferrer),
 | ||
|         mLocalizedStrs(aLocalizedStrs),
 | ||
|         mParentRuntime(
 | ||
|             JS_GetParentRuntime(CycleCollectedJSContext::Get()->Context())) {
 | ||
|     MOZ_ASSERT(NS_IsMainThread());
 | ||
|     MOZ_ASSERT(mParentRuntime);
 | ||
|     xpc::SetPrefableContextOptions(mContextOptions);
 | ||
|   }
 | ||
| 
 | ||
|   ~StartModuleLoadRunnable() = default;
 | ||
| 
 | ||
|   NS_IMETHOD Run() override;
 | ||
| 
 | ||
|  private:
 | ||
|   NS_IMETHOD RunOnWorkletThread();
 | ||
| 
 | ||
|   RefPtr<WorkletImpl> mWorkletImpl;
 | ||
|   nsMainThreadPtrHandle<WorkletFetchHandler> mHandlerRef;
 | ||
|   nsCOMPtr<nsIURI> mURI;
 | ||
|   nsCOMPtr<nsIURI> mReferrer;
 | ||
|   const nsTArray<nsString>& mLocalizedStrs;
 | ||
|   JSRuntime* mParentRuntime;
 | ||
|   JS::ContextOptions mContextOptions;
 | ||
| };
 | ||
| 
 | ||
| NS_IMETHODIMP
 | ||
| StartModuleLoadRunnable::Run() {
 | ||
|   // WorkletThread::IsOnWorkletThread() cannot be used here because it depends
 | ||
|   // on a WorkletJSContext having been created for this thread.  That does not
 | ||
|   // happen until the global scope is created the first time
 | ||
|   // RunOnWorkletThread() is called.
 | ||
|   MOZ_ASSERT(!NS_IsMainThread());
 | ||
|   return RunOnWorkletThread();
 | ||
| }
 | ||
| 
 | ||
| NS_IMETHODIMP StartModuleLoadRunnable::RunOnWorkletThread() {
 | ||
|   // This can be called on a GraphRunner thread or a DOM Worklet thread.
 | ||
|   WorkletThread::EnsureCycleCollectedJSContext(mParentRuntime, mContextOptions);
 | ||
| 
 | ||
|   WorkletGlobalScope* globalScope = mWorkletImpl->GetGlobalScope();
 | ||
|   if (!globalScope) {
 | ||
|     return NS_ERROR_DOM_UNKNOWN_ERR;
 | ||
|   }
 | ||
| 
 | ||
|   // To fetch a worklet/module worker script graph:
 | ||
|   // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-worklet/module-worker-script-graph
 | ||
|   // Step 1. Let options be a script fetch options whose cryptographic nonce is
 | ||
|   // the empty string, integrity metadata is the empty string, parser metadata
 | ||
|   // is "not-parser-inserted", credentials mode is credentials mode, referrer
 | ||
|   // policy is the empty string, and fetch priority is "auto".
 | ||
|   RefPtr<ScriptFetchOptions> fetchOptions = new ScriptFetchOptions(
 | ||
|       CORSMode::CORS_NONE, /* aNonce = */ u""_ns, RequestPriority::Auto,
 | ||
|       ParserMetadata::NotParserInserted,
 | ||
|       /*triggeringPrincipal*/ nullptr);
 | ||
| 
 | ||
|   WorkletModuleLoader* moduleLoader =
 | ||
|       static_cast<WorkletModuleLoader*>(globalScope->GetModuleLoader());
 | ||
|   MOZ_ASSERT(moduleLoader);
 | ||
| 
 | ||
|   if (!moduleLoader->HasSetLocalizedStrings()) {
 | ||
|     moduleLoader->SetLocalizedStrings(&mLocalizedStrs);
 | ||
|   }
 | ||
| 
 | ||
|   RefPtr<WorkletLoadContext> loadContext = new WorkletLoadContext(mHandlerRef);
 | ||
| 
 | ||
|   RefPtr<JS::loader::VisitedURLSet> visitedSet =
 | ||
|       ModuleLoadRequest::NewVisitedSetForTopLevelImport(
 | ||
|           mURI, JS::ModuleType::JavaScript);
 | ||
| 
 | ||
|   // Part of Step 2. This sets the Top-level flag to true
 | ||
|   RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
 | ||
|       mURI, JS::ModuleType::JavaScript, ReferrerPolicy::_empty, fetchOptions,
 | ||
|       SRIMetadata(), mReferrer, loadContext, ModuleLoadRequest::Kind::TopLevel,
 | ||
|       moduleLoader, visitedSet, nullptr);
 | ||
| 
 | ||
|   request->mURL = request->mURI->GetSpecOrDefault();
 | ||
|   request->NoCacheEntryFound();
 | ||
| 
 | ||
|   return request->StartModuleLoad();
 | ||
| }
 | ||
| 
 | ||
| StartFetchRunnable::StartFetchRunnable(
 | ||
|     const nsMainThreadPtrHandle<WorkletFetchHandler>& aHandlerRef, nsIURI* aURI,
 | ||
|     nsIURI* aReferrer)
 | ||
|     : Runnable("Worklet::StartFetchRunnable"),
 | ||
|       mHandlerRef(aHandlerRef),
 | ||
|       mURI(aURI),
 | ||
|       mReferrer(aReferrer) {
 | ||
|   MOZ_ASSERT(!NS_IsMainThread());
 | ||
| }
 | ||
| 
 | ||
| NS_IMETHODIMP
 | ||
| StartFetchRunnable::Run() {
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
| 
 | ||
|   nsCOMPtr<nsIGlobalObject> global =
 | ||
|       do_QueryInterface(mHandlerRef->mWorklet->GetParentObject());
 | ||
|   MOZ_ASSERT(global);
 | ||
| 
 | ||
|   AutoJSAPI jsapi;
 | ||
|   if (NS_WARN_IF(!jsapi.Init(global))) {
 | ||
|     return NS_ERROR_FAILURE;
 | ||
|   }
 | ||
| 
 | ||
|   JSContext* cx = jsapi.cx();
 | ||
|   nsresult rv = mHandlerRef->StartFetch(cx, mURI, mReferrer);
 | ||
|   if (NS_FAILED(rv)) {
 | ||
|     mHandlerRef->HandleFetchFailed(mURI);
 | ||
|     return NS_ERROR_FAILURE;
 | ||
|   }
 | ||
| 
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| // A Runnable to call ModuleLoadRequest::OnFetchComplete on a worklet thread.
 | ||
| class FetchCompleteRunnable final : public Runnable {
 | ||
|  public:
 | ||
|   FetchCompleteRunnable(WorkletImpl* aWorkletImpl, nsIURI* aURI,
 | ||
|                         nsresult aResult,
 | ||
|                         UniquePtr<uint8_t[]> aScriptBuffer = nullptr,
 | ||
|                         size_t aScriptLength = 0)
 | ||
|       : Runnable("Worklet::FetchCompleteRunnable"),
 | ||
|         mWorkletImpl(aWorkletImpl),
 | ||
|         mURI(aURI),
 | ||
|         mResult(aResult),
 | ||
|         mScriptBuffer(std::move(aScriptBuffer)),
 | ||
|         mScriptLength(aScriptLength) {
 | ||
|     MOZ_ASSERT(NS_IsMainThread());
 | ||
|   }
 | ||
| 
 | ||
|   ~FetchCompleteRunnable() = default;
 | ||
| 
 | ||
|   NS_IMETHOD Run() override;
 | ||
| 
 | ||
|  private:
 | ||
|   NS_IMETHOD RunOnWorkletThread();
 | ||
| 
 | ||
|   RefPtr<WorkletImpl> mWorkletImpl;
 | ||
|   nsCOMPtr<nsIURI> mURI;
 | ||
|   nsresult mResult;
 | ||
|   UniquePtr<uint8_t[]> mScriptBuffer;
 | ||
|   size_t mScriptLength;
 | ||
| };
 | ||
| 
 | ||
| NS_IMETHODIMP
 | ||
| FetchCompleteRunnable::Run() {
 | ||
|   MOZ_ASSERT(WorkletThread::IsOnWorkletThread());
 | ||
|   return RunOnWorkletThread();
 | ||
| }
 | ||
| 
 | ||
| NS_IMETHODIMP FetchCompleteRunnable::RunOnWorkletThread() {
 | ||
|   WorkletGlobalScope* globalScope = mWorkletImpl->GetGlobalScope();
 | ||
|   if (!globalScope) {
 | ||
|     return NS_ERROR_DOM_UNKNOWN_ERR;
 | ||
|   }
 | ||
| 
 | ||
|   WorkletModuleLoader* moduleLoader =
 | ||
|       static_cast<WorkletModuleLoader*>(globalScope->GetModuleLoader());
 | ||
|   MOZ_ASSERT(moduleLoader);
 | ||
|   MOZ_ASSERT(mURI);
 | ||
|   ModuleLoadRequest* request = moduleLoader->GetRequest(mURI);
 | ||
|   MOZ_ASSERT(request);
 | ||
| 
 | ||
|   // Set the Source type to "text" for decoding.
 | ||
|   request->SetTextSource(request->mLoadContext.get());
 | ||
| 
 | ||
|   nsresult rv;
 | ||
|   if (mScriptBuffer) {
 | ||
|     UniquePtr<ScriptDecoder> decoder = MakeUnique<ScriptDecoder>(
 | ||
|         UTF_8_ENCODING, ScriptDecoder::BOMHandling::Remove);
 | ||
|     rv = decoder->DecodeRawData(request, mScriptBuffer.get(), mScriptLength,
 | ||
|                                 true);
 | ||
|     NS_ENSURE_SUCCESS(rv, rv);
 | ||
|   }
 | ||
| 
 | ||
|   request->mBaseURL = mURI;
 | ||
|   request->OnFetchComplete(mResult);
 | ||
| 
 | ||
|   if (NS_FAILED(mResult)) {
 | ||
|     if (request->IsTopLevel()) {
 | ||
|       request->LoadFailed();
 | ||
|     } else {
 | ||
|       request->Cancel();
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   moduleLoader->RemoveRequest(mURI);
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| //////////////////////////////////////////////////////////////
 | ||
| // WorkletFetchHandler
 | ||
| //////////////////////////////////////////////////////////////
 | ||
| NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkletFetchHandler)
 | ||
| NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkletFetchHandler)
 | ||
| 
 | ||
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkletFetchHandler)
 | ||
|   NS_INTERFACE_MAP_ENTRY(nsISupports)
 | ||
| NS_INTERFACE_MAP_END
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTION_CLASS(WorkletFetchHandler)
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WorkletFetchHandler)
 | ||
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWorklet, mPromises)
 | ||
|   tmp->mErrorToRethrow.setUndefined();
 | ||
| NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WorkletFetchHandler)
 | ||
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWorklet, mPromises)
 | ||
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WorkletFetchHandler)
 | ||
|   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mErrorToRethrow)
 | ||
| NS_IMPL_CYCLE_COLLECTION_TRACE_END
 | ||
| 
 | ||
| // static
 | ||
| already_AddRefed<Promise> WorkletFetchHandler::AddModule(
 | ||
|     Worklet* aWorklet, JSContext* aCx, const nsAString& aModuleURL,
 | ||
|     const WorkletOptions& aOptions, ErrorResult& aRv) {
 | ||
|   MOZ_ASSERT(aWorklet);
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
| 
 | ||
|   aWorklet->Impl()->OnAddModuleStarted();
 | ||
| 
 | ||
|   auto promiseSettledGuard =
 | ||
|       MakeScopeExit([&] { aWorklet->Impl()->OnAddModulePromiseSettled(); });
 | ||
| 
 | ||
|   nsCOMPtr<nsIGlobalObject> global =
 | ||
|       do_QueryInterface(aWorklet->GetParentObject());
 | ||
|   MOZ_ASSERT(global);
 | ||
| 
 | ||
|   RefPtr<Promise> promise = Promise::Create(global, aRv);
 | ||
|   if (NS_WARN_IF(aRv.Failed())) {
 | ||
|     return nullptr;
 | ||
|   }
 | ||
| 
 | ||
|   nsCOMPtr<nsPIDOMWindowInner> window = aWorklet->GetParentObject();
 | ||
|   MOZ_ASSERT(window);
 | ||
| 
 | ||
|   nsCOMPtr<Document> doc;
 | ||
|   doc = window->GetExtantDoc();
 | ||
|   if (!doc) {
 | ||
|     promise->MaybeReject(NS_ERROR_FAILURE);
 | ||
|     return promise.forget();
 | ||
|   }
 | ||
| 
 | ||
|   nsCOMPtr<nsIURI> resolvedURI;
 | ||
|   nsresult rv = NS_NewURI(getter_AddRefs(resolvedURI), aModuleURL, nullptr,
 | ||
|                           doc->GetBaseURI());
 | ||
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|     // https://html.spec.whatwg.org/multipage/worklets.html#dom-worklet-addmodule
 | ||
|     // Step 3. If this fails, then return a promise rejected with a
 | ||
|     // "SyntaxError" DOMException.
 | ||
|     rv = NS_ERROR_DOM_SYNTAX_ERR;
 | ||
| 
 | ||
|     promise->MaybeReject(rv);
 | ||
|     return promise.forget();
 | ||
|   }
 | ||
| 
 | ||
|   nsAutoCString spec;
 | ||
|   rv = resolvedURI->GetSpec(spec);
 | ||
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|     rv = NS_ERROR_DOM_SYNTAX_ERR;
 | ||
| 
 | ||
|     promise->MaybeReject(rv);
 | ||
|     return promise.forget();
 | ||
|   }
 | ||
| 
 | ||
|   // Maybe we already have an handler for this URI
 | ||
|   {
 | ||
|     WorkletFetchHandler* handler = aWorklet->GetImportFetchHandler(spec);
 | ||
|     if (handler) {
 | ||
|       handler->AddPromise(aCx, promise);
 | ||
|       return promise.forget();
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   RefPtr<WorkletFetchHandler> handler =
 | ||
|       new WorkletFetchHandler(aWorklet, promise, aOptions.mCredentials);
 | ||
| 
 | ||
|   nsMainThreadPtrHandle<WorkletFetchHandler> handlerRef{
 | ||
|       new nsMainThreadPtrHolder<WorkletFetchHandler>("FetchHandler", handler)};
 | ||
| 
 | ||
|   // Determine request's Referrer
 | ||
|   // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
 | ||
|   // Step 3.  Switch on request’s referrer:
 | ||
|   //    "client"
 | ||
|   //    Step 1.4. Let referrerSource be document’s URL.
 | ||
|   nsIURI* referrer = doc->GetDocumentURIAsReferrer();
 | ||
|   nsCOMPtr<nsIRunnable> runnable = new StartModuleLoadRunnable(
 | ||
|       aWorklet->mImpl, handlerRef, std::move(resolvedURI), referrer,
 | ||
|       aWorklet->GetLocalizedStrings());
 | ||
| 
 | ||
|   if (NS_FAILED(aWorklet->mImpl->SendControlMessage(runnable.forget()))) {
 | ||
|     return nullptr;
 | ||
|   }
 | ||
| 
 | ||
|   promiseSettledGuard.release();
 | ||
| 
 | ||
|   aWorklet->AddImportFetchHandler(spec, handler);
 | ||
|   return promise.forget();
 | ||
| }
 | ||
| 
 | ||
| WorkletFetchHandler::WorkletFetchHandler(Worklet* aWorklet, Promise* aPromise,
 | ||
|                                          RequestCredentials aCredentials)
 | ||
|     : mWorklet(aWorklet), mStatus(ePending), mCredentials(aCredentials) {
 | ||
|   MOZ_ASSERT(aWorklet);
 | ||
|   MOZ_ASSERT(aPromise);
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
| 
 | ||
|   mPromises.AppendElement(aPromise);
 | ||
| }
 | ||
| 
 | ||
| WorkletFetchHandler::~WorkletFetchHandler() { mozilla::DropJSObjects(this); }
 | ||
| 
 | ||
| void WorkletFetchHandler::ExecutionFailed() {
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
|   RejectPromises(NS_ERROR_DOM_ABORT_ERR);
 | ||
| }
 | ||
| 
 | ||
| void WorkletFetchHandler::ExecutionFailed(JS::Handle<JS::Value> aError) {
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
|   RejectPromises(aError);
 | ||
| }
 | ||
| 
 | ||
| void WorkletFetchHandler::ExecutionSucceeded() {
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
|   ResolvePromises();
 | ||
| }
 | ||
| 
 | ||
| void WorkletFetchHandler::AddPromise(JSContext* aCx, Promise* aPromise) {
 | ||
|   MOZ_ASSERT(aPromise);
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
| 
 | ||
|   switch (mStatus) {
 | ||
|     case ePending:
 | ||
|       mPromises.AppendElement(aPromise);
 | ||
|       return;
 | ||
| 
 | ||
|     case eRejected:
 | ||
|       if (mHasError) {
 | ||
|         JS::Rooted<JS::Value> error(aCx, mErrorToRethrow);
 | ||
|         aPromise->MaybeReject(error);
 | ||
|       } else {
 | ||
|         aPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
 | ||
|       }
 | ||
|       return;
 | ||
| 
 | ||
|     case eResolved:
 | ||
|       aPromise->MaybeResolveWithUndefined();
 | ||
|       return;
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| void WorkletFetchHandler::RejectPromises(nsresult aResult) {
 | ||
|   MOZ_ASSERT(mStatus == ePending);
 | ||
|   MOZ_ASSERT(NS_FAILED(aResult));
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
| 
 | ||
|   mWorklet->Impl()->OnAddModulePromiseSettled();
 | ||
| 
 | ||
|   for (uint32_t i = 0; i < mPromises.Length(); ++i) {
 | ||
|     mPromises[i]->MaybeReject(aResult);
 | ||
|   }
 | ||
|   mPromises.Clear();
 | ||
| 
 | ||
|   mStatus = eRejected;
 | ||
|   mWorklet = nullptr;
 | ||
| }
 | ||
| 
 | ||
| void WorkletFetchHandler::RejectPromises(JS::Handle<JS::Value> aValue) {
 | ||
|   MOZ_ASSERT(mStatus == ePending);
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
| 
 | ||
|   mWorklet->Impl()->OnAddModulePromiseSettled();
 | ||
| 
 | ||
|   for (uint32_t i = 0; i < mPromises.Length(); ++i) {
 | ||
|     mPromises[i]->MaybeReject(aValue);
 | ||
|   }
 | ||
|   mPromises.Clear();
 | ||
| 
 | ||
|   mHasError = true;
 | ||
|   mErrorToRethrow = aValue;
 | ||
| 
 | ||
|   mozilla::HoldJSObjects(this);
 | ||
| 
 | ||
|   mStatus = eRejected;
 | ||
|   mWorklet = nullptr;
 | ||
| }
 | ||
| 
 | ||
| void WorkletFetchHandler::ResolvePromises() {
 | ||
|   MOZ_ASSERT(mStatus == ePending);
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
| 
 | ||
|   mWorklet->Impl()->OnAddModulePromiseSettled();
 | ||
| 
 | ||
|   for (uint32_t i = 0; i < mPromises.Length(); ++i) {
 | ||
|     mPromises[i]->MaybeResolveWithUndefined();
 | ||
|   }
 | ||
|   mPromises.Clear();
 | ||
| 
 | ||
|   mStatus = eResolved;
 | ||
|   mWorklet = nullptr;
 | ||
| }
 | ||
| 
 | ||
| nsresult WorkletFetchHandler::StartFetch(JSContext* aCx, nsIURI* aURI,
 | ||
|                                          nsIURI* aReferrer) {
 | ||
|   nsAutoCString spec;
 | ||
|   nsresult res = aURI->GetSpec(spec);
 | ||
|   if (NS_WARN_IF(NS_FAILED(res))) {
 | ||
|     return NS_ERROR_FAILURE;
 | ||
|   }
 | ||
| 
 | ||
|   RequestOrUTF8String requestInput;
 | ||
|   requestInput.SetAsUTF8String().ShareOrDependUpon(spec);
 | ||
| 
 | ||
|   RootedDictionary<RequestInit> requestInit(aCx);
 | ||
|   requestInit.mCredentials.Construct(mCredentials);
 | ||
| 
 | ||
|   // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
 | ||
|   // Step 8. mode is "cors"
 | ||
|   requestInit.mMode.Construct(RequestMode::Cors);
 | ||
| 
 | ||
|   if (aReferrer) {
 | ||
|     res = aReferrer->GetSpec(requestInit.mReferrer.Construct());
 | ||
|     if (NS_WARN_IF(NS_FAILED(res))) {
 | ||
|       return NS_ERROR_FAILURE;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   nsCOMPtr<nsIGlobalObject> global =
 | ||
|       do_QueryInterface(mWorklet->GetParentObject());
 | ||
|   MOZ_ASSERT(global);
 | ||
| 
 | ||
|   // Note: added to infer a default credentials mode in the Request setup,
 | ||
|   // but we always pass an explicit credentials value in requestInit, so
 | ||
|   // this has no effect right now. Bug 1887862 covers fixing worklets to behave
 | ||
|   // the same as "normal" fetch calls.
 | ||
|   nsIPrincipal* p = global->PrincipalOrNull();
 | ||
|   CallerType callerType = (p && p->IsSystemPrincipal() ? CallerType::System
 | ||
|                                                        : CallerType::NonSystem);
 | ||
|   IgnoredErrorResult rv;
 | ||
|   SafeRefPtr<Request> request = Request::Constructor(
 | ||
|       global, aCx, requestInput, requestInit, callerType, rv);
 | ||
|   if (rv.Failed()) {
 | ||
|     return NS_ERROR_FAILURE;
 | ||
|   }
 | ||
| 
 | ||
|   request->OverrideContentPolicyType(mWorklet->Impl()->ContentPolicyType());
 | ||
| 
 | ||
|   RequestOrUTF8String finalRequestInput;
 | ||
|   finalRequestInput.SetAsRequest() = request.unsafeGetRawPtr();
 | ||
| 
 | ||
|   RefPtr<Promise> fetchPromise = FetchRequest(
 | ||
|       global, finalRequestInput, requestInit, CallerType::System, rv);
 | ||
|   if (NS_WARN_IF(rv.Failed())) {
 | ||
|     return NS_ERROR_FAILURE;
 | ||
|   }
 | ||
| 
 | ||
|   RefPtr<WorkletScriptHandler> scriptHandler =
 | ||
|       new WorkletScriptHandler(mWorklet, aURI);
 | ||
|   fetchPromise->AppendNativeHandler(scriptHandler);
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| void WorkletFetchHandler::HandleFetchFailed(nsIURI* aURI) {
 | ||
|   nsCOMPtr<nsIRunnable> runnable = new FetchCompleteRunnable(
 | ||
|       mWorklet->mImpl, aURI, NS_ERROR_FAILURE, nullptr, 0);
 | ||
| 
 | ||
|   if (NS_WARN_IF(
 | ||
|           NS_FAILED(mWorklet->mImpl->SendControlMessage(runnable.forget())))) {
 | ||
|     NS_WARNING("Failed to dispatch FetchCompleteRunnable to a worklet thread.");
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| //////////////////////////////////////////////////////////////
 | ||
| // WorkletScriptHandler
 | ||
| //////////////////////////////////////////////////////////////
 | ||
| NS_IMPL_ISUPPORTS(WorkletScriptHandler, nsIStreamLoaderObserver)
 | ||
| 
 | ||
| WorkletScriptHandler::WorkletScriptHandler(Worklet* aWorklet, nsIURI* aURI)
 | ||
|     : mWorklet(aWorklet), mURI(aURI) {}
 | ||
| 
 | ||
| void WorkletScriptHandler::ResolvedCallback(JSContext* aCx,
 | ||
|                                             JS::Handle<JS::Value> aValue,
 | ||
|                                             ErrorResult& aRv) {
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
| 
 | ||
|   if (!aValue.isObject()) {
 | ||
|     HandleFailure(NS_ERROR_FAILURE);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   RefPtr<Response> response;
 | ||
|   nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
 | ||
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|     HandleFailure(NS_ERROR_FAILURE);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   // https://html.spec.whatwg.org/multipage/worklets.html#dom-worklet-addmodule
 | ||
|   // Step 6.4.1. If script is null, then:
 | ||
|   //   Step 1.1.2. Reject promise with an "AbortError" DOMException.
 | ||
|   if (!response->Ok()) {
 | ||
|     HandleFailure(NS_ERROR_DOM_ABORT_ERR);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   nsCOMPtr<nsIInputStream> inputStream;
 | ||
|   response->GetBody(getter_AddRefs(inputStream));
 | ||
|   if (!inputStream) {
 | ||
|     HandleFailure(NS_ERROR_DOM_NETWORK_ERR);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   nsCOMPtr<nsIInputStreamPump> pump;
 | ||
|   rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream.forget());
 | ||
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|     HandleFailure(rv);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   nsCOMPtr<nsIStreamLoader> loader;
 | ||
|   rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
 | ||
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|     HandleFailure(rv);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   rv = pump->AsyncRead(loader);
 | ||
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|     HandleFailure(rv);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump);
 | ||
|   if (rr) {
 | ||
|     nsCOMPtr<nsIEventTarget> sts =
 | ||
|         do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
 | ||
|     RefPtr<TaskQueue> queue = TaskQueue::Create(
 | ||
|         sts.forget(), "WorkletScriptHandler STS Delivery Queue");
 | ||
|     rv = rr->RetargetDeliveryTo(queue);
 | ||
|     if (NS_FAILED(rv)) {
 | ||
|       NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| NS_IMETHODIMP WorkletScriptHandler::OnStreamComplete(nsIStreamLoader* aLoader,
 | ||
|                                                      nsISupports* aContext,
 | ||
|                                                      nsresult aStatus,
 | ||
|                                                      uint32_t aStringLen,
 | ||
|                                                      const uint8_t* aString) {
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
| 
 | ||
|   if (NS_FAILED(aStatus)) {
 | ||
|     HandleFailure(aStatus);
 | ||
|     return NS_OK;
 | ||
|   }
 | ||
| 
 | ||
|   // Copy the buffer and decode it on worklet thread, as we can only access
 | ||
|   // ModuleLoadRequest on worklet thread.
 | ||
|   UniquePtr<uint8_t[]> scriptTextBuf = MakeUnique<uint8_t[]>(aStringLen);
 | ||
|   memcpy(scriptTextBuf.get(), aString, aStringLen);
 | ||
| 
 | ||
|   nsCOMPtr<nsIRunnable> runnable = new FetchCompleteRunnable(
 | ||
|       mWorklet->mImpl, mURI, NS_OK, std::move(scriptTextBuf), aStringLen);
 | ||
| 
 | ||
|   if (NS_FAILED(mWorklet->mImpl->SendControlMessage(runnable.forget()))) {
 | ||
|     HandleFailure(NS_ERROR_FAILURE);
 | ||
|   }
 | ||
| 
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| void WorkletScriptHandler::RejectedCallback(JSContext* aCx,
 | ||
|                                             JS::Handle<JS::Value> aValue,
 | ||
|                                             ErrorResult& aRv) {
 | ||
|   MOZ_ASSERT(NS_IsMainThread());
 | ||
| 
 | ||
|   // https://html.spec.whatwg.org/multipage/worklets.html#dom-worklet-addmodule
 | ||
|   // Step 6.4.1. If script is null, then:
 | ||
|   //   Step 1.1.2. Reject promise with an "AbortError" DOMException.
 | ||
|   HandleFailure(NS_ERROR_DOM_ABORT_ERR);
 | ||
| }
 | ||
| 
 | ||
| void WorkletScriptHandler::HandleFailure(nsresult aResult) {
 | ||
|   DispatchFetchCompleteToWorklet(aResult);
 | ||
| }
 | ||
| 
 | ||
| void WorkletScriptHandler::DispatchFetchCompleteToWorklet(nsresult aRv) {
 | ||
|   nsCOMPtr<nsIRunnable> runnable =
 | ||
|       new FetchCompleteRunnable(mWorklet->mImpl, mURI, aRv, nullptr, 0);
 | ||
| 
 | ||
|   if (NS_WARN_IF(
 | ||
|           NS_FAILED(mWorklet->mImpl->SendControlMessage(runnable.forget())))) {
 | ||
|     NS_WARNING("Failed to dispatch FetchCompleteRunnable to a worklet thread.");
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| }  // namespace mozilla::dom
 | 
