/* -*- 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 "WorkletModuleLoader.h" #include "js/CompileOptions.h" // JS::InstantiateOptions #include "js/experimental/JSStencil.h" // JS::CompileModuleScriptToStencil, JS::InstantiateModuleStencil #include "js/loader/ModuleLoadRequest.h" #include "mozilla/ScopeExit.h" #include "mozilla/Unused.h" #include "mozilla/dom/StructuredCloneHolder.h" #include "mozilla/dom/Worklet.h" #include "mozilla/dom/WorkletFetchHandler.h" #include "nsStringBundle.h" using JS::loader::ModuleLoadRequest; using JS::loader::ResolveError; namespace mozilla::dom::loader { ////////////////////////////////////////////////////////////// // WorkletScriptLoader ////////////////////////////////////////////////////////////// NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkletScriptLoader) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION(WorkletScriptLoader) NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkletScriptLoader) NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkletScriptLoader) ////////////////////////////////////////////////////////////// // WorkletModuleLoader ////////////////////////////////////////////////////////////// NS_IMPL_ADDREF_INHERITED(WorkletModuleLoader, ModuleLoaderBase) NS_IMPL_RELEASE_INHERITED(WorkletModuleLoader, ModuleLoaderBase) NS_IMPL_CYCLE_COLLECTION_INHERITED(WorkletModuleLoader, ModuleLoaderBase, mFetchingRequests) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkletModuleLoader) NS_INTERFACE_MAP_END_INHERITING(ModuleLoaderBase) WorkletModuleLoader::WorkletModuleLoader(WorkletScriptLoader* aScriptLoader, nsIGlobalObject* aGlobalObject) : ModuleLoaderBase(aScriptLoader, aGlobalObject, GetCurrentSerialEventTarget()) { // This should be constructed on a worklet thread. MOZ_ASSERT(!NS_IsMainThread()); } already_AddRefed WorkletModuleLoader::CreateStaticImport( nsIURI* aURI, ModuleLoadRequest* aParent) { const nsMainThreadPtrHandle& handlerRef = aParent->GetWorkletLoadContext()->GetHandlerRef(); RefPtr loadContext = new WorkletLoadContext(handlerRef); // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-a-module-script // Step 11. Perform the internal module script graph fetching procedure // // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure // Step 5. Fetch a single module script with referrer is referringScript's // base URL, nsIURI* referrer = aParent->mURI; RefPtr request = new ModuleLoadRequest( aURI, aParent->mFetchOptions, SRIMetadata(), referrer, loadContext, false, /* is top level */ false, /* is dynamic import */ this, aParent->mVisitedSet, aParent->GetRootModule()); request->mURL = request->mURI->GetSpecOrDefault(); return request.forget(); } already_AddRefed WorkletModuleLoader::CreateDynamicImport( JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript, JS::Handle aReferencingPrivate, JS::Handle aSpecifier, JS::Handle aPromise) { return nullptr; } bool WorkletModuleLoader::CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) { return true; } nsresult WorkletModuleLoader::StartFetch(ModuleLoadRequest* aRequest) { InsertRequest(aRequest->mURI, aRequest); RefPtr runnable = new StartFetchRunnable(aRequest->GetWorkletLoadContext()->GetHandlerRef(), aRequest->mURI, aRequest->mReferrer); NS_DispatchToMainThread(runnable.forget()); return NS_OK; } nsresult WorkletModuleLoader::CompileFetchedModule( JSContext* aCx, JS::Handle aGlobal, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest, JS::MutableHandle aModuleScript) { RefPtr stencil; MOZ_ASSERT(aRequest->IsTextSource()); MaybeSourceText maybeSource; nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource); NS_ENSURE_SUCCESS(rv, rv); auto compile = [&](auto& source) { return JS::CompileModuleScriptToStencil(aCx, aOptions, source); }; stencil = maybeSource.mapNonEmpty(compile); if (!stencil) { return NS_ERROR_FAILURE; } JS::InstantiateOptions instantiateOptions(aOptions); aModuleScript.set( JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil)); return aModuleScript ? NS_OK : NS_ERROR_FAILURE; } // AddModuleResultRunnable is a Runnable which will notify the result of // Worklet::AddModule on the main thread. class AddModuleResultRunnable final : public Runnable { public: explicit AddModuleResultRunnable( const nsMainThreadPtrHandle& aHandlerRef, bool aSucceeded) : Runnable("Worklet::AddModuleResultRunnable"), mHandlerRef(aHandlerRef), mSucceeded(aSucceeded) { MOZ_ASSERT(!NS_IsMainThread()); } ~AddModuleResultRunnable() = default; NS_IMETHOD Run() override; private: nsMainThreadPtrHandle mHandlerRef; bool mSucceeded; }; NS_IMETHODIMP AddModuleResultRunnable::Run() { MOZ_ASSERT(NS_IsMainThread()); if (mSucceeded) { mHandlerRef->ExecutionSucceeded(); } else { mHandlerRef->ExecutionFailed(); } return NS_OK; } class AddModuleThrowErrorRunnable final : public Runnable, public StructuredCloneHolder { public: explicit AddModuleThrowErrorRunnable( const nsMainThreadPtrHandle& aHandlerRef) : Runnable("Worklet::AddModuleThrowErrorRunnable"), StructuredCloneHolder(CloningSupported, TransferringNotSupported, StructuredCloneScope::SameProcess), mHandlerRef(aHandlerRef) { MOZ_ASSERT(!NS_IsMainThread()); } ~AddModuleThrowErrorRunnable() = default; NS_IMETHOD Run() override; private: nsMainThreadPtrHandle mHandlerRef; }; NS_IMETHODIMP AddModuleThrowErrorRunnable::Run() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr global = do_QueryInterface(mHandlerRef->mWorklet->GetParentObject()); MOZ_ASSERT(global); AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(global))) { mHandlerRef->ExecutionFailed(); return NS_ERROR_FAILURE; } JSContext* cx = jsapi.cx(); JS::Rooted error(cx); ErrorResult result; Read(global, cx, &error, result); Unused << NS_WARN_IF(result.Failed()); mHandlerRef->ExecutionFailed(error); return NS_OK; } void WorkletModuleLoader::OnModuleLoadComplete(ModuleLoadRequest* aRequest) { if (!aRequest->IsTopLevel()) { return; } const nsMainThreadPtrHandle& handlerRef = aRequest->GetWorkletLoadContext()->GetHandlerRef(); auto addModuleFailed = MakeScopeExit([&] { RefPtr runnable = new AddModuleResultRunnable(handlerRef, false); NS_DispatchToMainThread(runnable.forget()); }); if (!aRequest->mModuleScript) { return; } if (!aRequest->InstantiateModuleGraph()) { return; } nsresult rv = aRequest->EvaluateModule(); if (NS_FAILED(rv)) { return; } bool hasError = aRequest->mModuleScript->HasErrorToRethrow(); if (hasError) { AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) { return; } JSContext* cx = jsapi.cx(); JS::Rooted error(cx, aRequest->mModuleScript->ErrorToRethrow()); RefPtr runnable = new AddModuleThrowErrorRunnable(handlerRef); ErrorResult result; runnable->Write(cx, error, result); if (NS_WARN_IF(result.Failed())) { return; } addModuleFailed.release(); NS_DispatchToMainThread(runnable.forget()); return; } addModuleFailed.release(); RefPtr runnable = new AddModuleResultRunnable(handlerRef, true); NS_DispatchToMainThread(runnable.forget()); } // TODO: Bug 1808301: Call FormatLocalizedString from a worklet thread. nsresult WorkletModuleLoader::GetResolveFailureMessage( ResolveError aError, const nsAString& aSpecifier, nsAString& aResult) { uint8_t index = static_cast(aError); MOZ_ASSERT(index < static_cast(ResolveError::Length)); MOZ_ASSERT(mLocalizedStrs); MOZ_ASSERT(!mLocalizedStrs->IsEmpty()); if (!mLocalizedStrs || NS_WARN_IF(mLocalizedStrs->IsEmpty())) { return NS_ERROR_FAILURE; } const nsString& localizedStr = mLocalizedStrs->ElementAt(index); AutoTArray params; params.AppendElement(aSpecifier); nsStringBundleBase::FormatString(localizedStr.get(), params, aResult); return NS_OK; } void WorkletModuleLoader::InsertRequest(nsIURI* aURI, ModuleLoadRequest* aRequest) { mFetchingRequests.InsertOrUpdate(aURI, aRequest); } void WorkletModuleLoader::RemoveRequest(nsIURI* aURI) { MOZ_ASSERT(mFetchingRequests.Remove(aURI)); } ModuleLoadRequest* WorkletModuleLoader::GetRequest(nsIURI* aURI) const { RefPtr req; MOZ_ALWAYS_TRUE(mFetchingRequests.Get(aURI, getter_AddRefs(req))); return req; } } // namespace mozilla::dom::loader