Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "WorkletModuleLoader.h"
#include "js/CompileOptions.h" // JS::InstantiateOptions
#include "js/experimental/JSStencil.h" // JS::CompileModuleScriptToStencil, JS::InstantiateModuleStencil
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#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) {
// This should be constructed on a worklet thread.
MOZ_ASSERT(!NS_IsMainThread());
}
already_AddRefed<ModuleLoadRequest> WorkletModuleLoader::CreateStaticImport(
nsIURI* aURI, JS::ModuleType aModuleType, ModuleLoadRequest* aParent) {
const nsMainThreadPtrHandle<WorkletFetchHandler>& handlerRef =
aParent->GetWorkletLoadContext()->GetHandlerRef();
RefPtr<WorkletLoadContext> loadContext = new WorkletLoadContext(handlerRef);
// Step 11. Perform the 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, aModuleType, aParent->ReferrerPolicy(), aParent->mFetchOptions,
SRIMetadata(), referrer, loadContext, false, /* is top level */
false, /* is dynamic import */
this, aParent->mVisitedSet, aParent->GetRootModule());
request->mURL = request->mURI->GetSpecOrDefault();
request->NoCacheEntryFound();
return request.forget();
}
already_AddRefed<ModuleLoadRequest> WorkletModuleLoader::CreateDynamicImport(
JSContext* aCx, nsIURI* aURI, JS::ModuleType aModuleType,
LoadedScript* aMaybeActiveScript, 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) {
switch (aRequest->mModuleType) {
case JS::ModuleType::Unknown:
MOZ_CRASH("Unexpected module type");
case JS::ModuleType::JavaScript:
return CompileJavaScriptModule(aCx, aOptions, aRequest, aModuleScript);
case JS::ModuleType::JSON:
return CompileJsonModule(aCx, aOptions, aRequest, aModuleScript);
}
MOZ_CRASH("Unhandled module type");
}
nsresult WorkletModuleLoader::CompileJavaScriptModule(
JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
JS::MutableHandle<JSObject*> aModuleScript) {
MOZ_ASSERT(aRequest->IsTextSource());
MaybeSourceText maybeSource;
nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource,
aRequest->mLoadContext.get());
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<JS::Stencil> stencil;
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;
}
nsresult WorkletModuleLoader::CompileJsonModule(
JSContext* aCx, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
JS::MutableHandle<JSObject*> aModuleScript) {
MOZ_ASSERT(aRequest->IsTextSource());
MaybeSourceText maybeSource;
nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource,
aRequest->mLoadContext.get());
NS_ENSURE_SUCCESS(rv, rv);
auto compile = [&](auto& source) {
return JS::CompileJsonModule(aCx, aOptions, source);
};
auto* jsonModule = maybeSource.mapNonEmpty(compile);
if (!jsonModule) {
return NS_ERROR_FAILURE;
}
aModuleScript.set(jsonModule);
return NS_OK;
}
// 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());
}
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