diff options
Diffstat (limited to 'dom/worklet/loader/WorkletModuleLoader.cpp')
-rw-r--r-- | dom/worklet/loader/WorkletModuleLoader.cpp | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/dom/worklet/loader/WorkletModuleLoader.cpp b/dom/worklet/loader/WorkletModuleLoader.cpp new file mode 100644 index 0000000000..34353107f8 --- /dev/null +++ b/dom/worklet/loader/WorkletModuleLoader.cpp @@ -0,0 +1,297 @@ +/* -*- 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<ModuleLoadRequest> WorkletModuleLoader::CreateStaticImport( + nsIURI* aURI, ModuleLoadRequest* aParent) { + const nsMainThreadPtrHandle<WorkletFetchHandler>& handlerRef = + aParent->GetWorkletLoadContext()->GetHandlerRef(); + RefPtr<WorkletLoadContext> 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<ModuleLoadRequest> 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<ModuleLoadRequest> WorkletModuleLoader::CreateDynamicImport( + JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript, + JS::Handle<JS::Value> aReferencingPrivate, JS::Handle<JSString*> aSpecifier, + JS::Handle<JSObject*> aPromise) { + return nullptr; +} + +bool WorkletModuleLoader::CanStartLoad(ModuleLoadRequest* aRequest, + nsresult* aRvOut) { + return true; +} + +nsresult WorkletModuleLoader::StartFetch(ModuleLoadRequest* aRequest) { + InsertRequest(aRequest->mURI, aRequest); + + RefPtr<StartFetchRunnable> runnable = + new StartFetchRunnable(aRequest->GetWorkletLoadContext()->GetHandlerRef(), + aRequest->mURI, aRequest->mReferrer); + NS_DispatchToMainThread(runnable.forget()); + return NS_OK; +} + +nsresult WorkletModuleLoader::CompileFetchedModule( + JSContext* aCx, JS::Handle<JSObject*> aGlobal, JS::CompileOptions& aOptions, + ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModuleScript) { + RefPtr<JS::Stencil> 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<WorkletFetchHandler>& aHandlerRef, + bool aSucceeded) + : Runnable("Worklet::AddModuleResultRunnable"), + mHandlerRef(aHandlerRef), + mSucceeded(aSucceeded) { + MOZ_ASSERT(!NS_IsMainThread()); + } + + ~AddModuleResultRunnable() = default; + + NS_IMETHOD + Run() override; + + private: + nsMainThreadPtrHandle<WorkletFetchHandler> 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<WorkletFetchHandler>& aHandlerRef) + : Runnable("Worklet::AddModuleThrowErrorRunnable"), + StructuredCloneHolder(CloningSupported, TransferringNotSupported, + StructuredCloneScope::SameProcess), + mHandlerRef(aHandlerRef) { + MOZ_ASSERT(!NS_IsMainThread()); + } + + ~AddModuleThrowErrorRunnable() = default; + + NS_IMETHOD + Run() override; + + private: + nsMainThreadPtrHandle<WorkletFetchHandler> mHandlerRef; +}; + +NS_IMETHODIMP +AddModuleThrowErrorRunnable::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))) { + mHandlerRef->ExecutionFailed(); + return NS_ERROR_FAILURE; + } + + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> 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<WorkletFetchHandler>& handlerRef = + aRequest->GetWorkletLoadContext()->GetHandlerRef(); + + auto addModuleFailed = MakeScopeExit([&] { + RefPtr<AddModuleResultRunnable> 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<JS::Value> error(cx, aRequest->mModuleScript->ErrorToRethrow()); + RefPtr<AddModuleThrowErrorRunnable> 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<AddModuleResultRunnable> 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<uint8_t>(aError); + MOZ_ASSERT(index < static_cast<uint8_t>(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<nsString, 1> 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<ModuleLoadRequest> req; + MOZ_ALWAYS_TRUE(mFetchingRequests.Get(aURI, getter_AddRefs(req))); + return req; +} + +} // namespace mozilla::dom::loader |