339 lines
11 KiB
C++
339 lines
11 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 "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 mozilla::dom::SRIMetadata& aSriMetadata) {
|
|
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, aModuleType, aParent->ReferrerPolicy(), aParent->mFetchOptions,
|
|
SRIMetadata(), referrer, loadContext,
|
|
ModuleLoadRequest::Kind::StaticImport, 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());
|
|
}
|
|
|
|
// 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
|