forked from mirrors/gecko-dev
260 lines
9.7 KiB
C++
260 lines
9.7 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* 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/. */
|
|
|
|
#ifndef mozilla_ScaffoldingCall_h
|
|
#define mozilla_ScaffoldingCall_h
|
|
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include "nsIGlobalObject.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "mozilla/MozPromise.h"
|
|
#include "mozilla/ResultVariant.h"
|
|
#include "mozilla/dom/OwnedRustBuffer.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/dom/ScaffoldingConverter.h"
|
|
#include "mozilla/dom/UniFFIBinding.h"
|
|
#include "mozilla/dom/UniFFIRust.h"
|
|
|
|
namespace mozilla::uniffi {
|
|
|
|
// Low-level result of calling a scaffolding function
|
|
//
|
|
// This stores what Rust returned in order to convert it into
|
|
// UniFFIScaffoldingCallResult
|
|
template <typename ReturnType>
|
|
struct RustCallResult {
|
|
ReturnType mReturnValue;
|
|
RustCallStatus mCallStatus = {};
|
|
};
|
|
|
|
template <>
|
|
struct RustCallResult<void> {
|
|
RustCallStatus mCallStatus = {};
|
|
};
|
|
|
|
// Does the work required to call a scaffolding function
|
|
//
|
|
// This class is generic over the type signature of the scaffolding function.
|
|
// This seems better than being generic over the functions themselves, since it
|
|
// saves space whenever 2 functions share a signature.
|
|
template <typename ReturnConverter, typename... ArgConverters>
|
|
class ScaffoldingCallHandler {
|
|
public:
|
|
// Pointer to a scaffolding function that can be called by this
|
|
// ScaffoldingConverter
|
|
using ScaffoldingFunc = typename ReturnConverter::RustType (*)(
|
|
typename ArgConverters::RustType..., RustCallStatus*);
|
|
|
|
// Perform an async scaffolding call
|
|
static already_AddRefed<dom::Promise> CallAsync(
|
|
ScaffoldingFunc aScaffoldingFunc, const dom::GlobalObject& aGlobal,
|
|
const dom::Sequence<dom::ScaffoldingType>& aArgs,
|
|
const nsLiteralCString& aFuncName, ErrorResult& aError) {
|
|
auto convertResult = ConvertJsArgs(aArgs);
|
|
if (convertResult.isErr()) {
|
|
aError.ThrowUnknownError(aFuncName + convertResult.unwrapErr());
|
|
return nullptr;
|
|
}
|
|
auto convertedArgs = convertResult.unwrap();
|
|
|
|
// Create the promise that we return to JS
|
|
nsCOMPtr<nsIGlobalObject> xpcomGlobal =
|
|
do_QueryInterface(aGlobal.GetAsSupports());
|
|
RefPtr<dom::Promise> returnPromise =
|
|
dom::Promise::Create(xpcomGlobal, aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Create a second promise that gets resolved by a background task that
|
|
// calls the scaffolding function
|
|
RefPtr taskPromise = new typename TaskPromiseType::Private(aFuncName.get());
|
|
nsresult dispatchResult = NS_DispatchBackgroundTask(
|
|
NS_NewRunnableFunction(aFuncName.get(),
|
|
[args = std::move(convertedArgs), taskPromise,
|
|
aScaffoldingFunc, aFuncName]() mutable {
|
|
auto callResult = CallScaffoldingFunc(
|
|
aScaffoldingFunc, std::move(args));
|
|
taskPromise->Resolve(std::move(callResult),
|
|
aFuncName.get());
|
|
}),
|
|
NS_DISPATCH_EVENT_MAY_BLOCK);
|
|
if (NS_FAILED(dispatchResult)) {
|
|
taskPromise->Reject(dispatchResult, aFuncName.get());
|
|
}
|
|
|
|
// When the background task promise completes, resolve the JS promise
|
|
taskPromise->Then(
|
|
GetCurrentSerialEventTarget(), aFuncName.get(),
|
|
[xpcomGlobal, returnPromise,
|
|
aFuncName](typename TaskPromiseType::ResolveOrRejectValue&& aResult) {
|
|
if (!aResult.IsResolve()) {
|
|
returnPromise->MaybeRejectWithUnknownError(aFuncName);
|
|
return;
|
|
}
|
|
|
|
dom::AutoEntryScript aes(xpcomGlobal, aFuncName.get());
|
|
dom::RootedDictionary<dom::UniFFIScaffoldingCallResult> returnValue(
|
|
aes.cx());
|
|
|
|
ReturnResult(aes.cx(), aResult.ResolveValue(), returnValue,
|
|
aFuncName);
|
|
returnPromise->MaybeResolve(returnValue);
|
|
});
|
|
|
|
// Return the JS promise, using forget() to convert it to already_AddRefed
|
|
return returnPromise.forget();
|
|
}
|
|
|
|
// Perform an sync scaffolding call
|
|
//
|
|
// aFuncName should be a literal C string
|
|
static void CallSync(
|
|
ScaffoldingFunc aScaffoldingFunc, const dom::GlobalObject& aGlobal,
|
|
const dom::Sequence<dom::ScaffoldingType>& aArgs,
|
|
dom::RootedDictionary<dom::UniFFIScaffoldingCallResult>& aReturnValue,
|
|
const nsLiteralCString& aFuncName, ErrorResult& aError) {
|
|
auto convertResult = ConvertJsArgs(aArgs);
|
|
if (convertResult.isErr()) {
|
|
aError.ThrowUnknownError(aFuncName + convertResult.unwrapErr());
|
|
return;
|
|
}
|
|
|
|
auto callResult = CallScaffoldingFunc(aScaffoldingFunc,
|
|
std::move(convertResult.unwrap()));
|
|
|
|
ReturnResult(aGlobal.Context(), callResult, aReturnValue, aFuncName);
|
|
}
|
|
|
|
private:
|
|
using RustArgs = std::tuple<typename ArgConverters::RustType...>;
|
|
using IntermediateArgs =
|
|
std::tuple<typename ArgConverters::IntermediateType...>;
|
|
using CallResult = RustCallResult<typename ReturnConverter::RustType>;
|
|
using TaskPromiseType = MozPromise<CallResult, nsresult, true>;
|
|
|
|
template <size_t I>
|
|
using NthArgConverter =
|
|
typename std::tuple_element<I, std::tuple<ArgConverters...>>::type;
|
|
|
|
// Convert arguments from JS
|
|
//
|
|
// This should be called in the main thread
|
|
static Result<IntermediateArgs, nsCString> ConvertJsArgs(
|
|
const dom::Sequence<dom::ScaffoldingType>& aArgs) {
|
|
IntermediateArgs convertedArgs;
|
|
if (aArgs.Length() != std::tuple_size_v<IntermediateArgs>) {
|
|
return mozilla::Err("Wrong argument count"_ns);
|
|
}
|
|
auto result = PrepareArgsHelper<0>(aArgs, convertedArgs);
|
|
return result.map([&](auto _) { return std::move(convertedArgs); });
|
|
}
|
|
|
|
// Helper function for PrepareArgs that uses c++ magic to help with iteration
|
|
template <size_t I = 0>
|
|
static Result<mozilla::Ok, nsCString> PrepareArgsHelper(
|
|
const dom::Sequence<dom::ScaffoldingType>& aArgs,
|
|
IntermediateArgs& aConvertedArgs) {
|
|
if constexpr (I >= sizeof...(ArgConverters)) {
|
|
// Iteration complete
|
|
return mozilla::Ok();
|
|
} else {
|
|
// Single iteration step
|
|
auto result = NthArgConverter<I>::FromJs(aArgs[I]);
|
|
if (result.isOk()) {
|
|
// The conversion worked, store our result and move on to the next
|
|
std::get<I>(aConvertedArgs) = result.unwrap();
|
|
return PrepareArgsHelper<I + 1>(aArgs, aConvertedArgs);
|
|
} else {
|
|
// The conversion failed, return an error and don't continue
|
|
return mozilla::Err(result.unwrapErr() +
|
|
nsPrintfCString(" (arg %zu)", I));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Call the scaffolding function
|
|
//
|
|
// For async calls this should be called in the worker thread
|
|
static CallResult CallScaffoldingFunc(ScaffoldingFunc aFunc,
|
|
IntermediateArgs&& aArgs) {
|
|
return CallScaffoldingFuncHelper(
|
|
aFunc, std::move(aArgs), std::index_sequence_for<ArgConverters...>());
|
|
}
|
|
|
|
// Helper function for CallScaffoldingFunc that uses c++ magic to help with
|
|
// iteration
|
|
template <size_t... Is>
|
|
static CallResult CallScaffoldingFuncHelper(ScaffoldingFunc aFunc,
|
|
IntermediateArgs&& aArgs,
|
|
std::index_sequence<Is...> seq) {
|
|
CallResult result;
|
|
|
|
auto makeCall = [&]() mutable {
|
|
return aFunc(
|
|
NthArgConverter<Is>::IntoRust(std::move(std::get<Is>(aArgs)))...,
|
|
&result.mCallStatus);
|
|
};
|
|
if constexpr (std::is_void_v<typename ReturnConverter::RustType>) {
|
|
makeCall();
|
|
} else {
|
|
result.mReturnValue = makeCall();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Return the result of the scaffolding call back to JS
|
|
//
|
|
// This should be called on the main thread
|
|
static void ReturnResult(
|
|
JSContext* aContext, CallResult& aCallResult,
|
|
dom::RootedDictionary<dom::UniFFIScaffoldingCallResult>& aReturnValue,
|
|
const nsLiteralCString& aFuncName) {
|
|
switch (aCallResult.mCallStatus.code) {
|
|
case RUST_CALL_SUCCESS: {
|
|
aReturnValue.mCode = dom::UniFFIScaffoldingCallCode::Success;
|
|
if constexpr (!std::is_void_v<typename ReturnConverter::RustType>) {
|
|
auto convertResult =
|
|
ReturnConverter::FromRust(aCallResult.mReturnValue);
|
|
if (convertResult.isOk()) {
|
|
ReturnConverter::IntoJs(aContext, std::move(convertResult.unwrap()),
|
|
aReturnValue.mData.Construct());
|
|
} else {
|
|
aReturnValue.mCode = dom::UniFFIScaffoldingCallCode::Internal_error;
|
|
aReturnValue.mInternalErrorMessage.Construct(
|
|
aFuncName + " converting result: "_ns +
|
|
convertResult.unwrapErr());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RUST_CALL_ERROR: {
|
|
// Rust Err() value. Populate data with the `RustBuffer` containing the
|
|
// error
|
|
aReturnValue.mCode = dom::UniFFIScaffoldingCallCode::Error;
|
|
aReturnValue.mData.Construct().SetAsArrayBuffer().Init(
|
|
OwnedRustBuffer(aCallResult.mCallStatus.error_buf)
|
|
.IntoArrayBuffer(aContext));
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
// This indicates a RustError, which shouldn't happen in practice since
|
|
// FF sets panic=abort
|
|
aReturnValue.mCode = dom::UniFFIScaffoldingCallCode::Internal_error;
|
|
aReturnValue.mInternalErrorMessage.Construct(aFuncName +
|
|
" Unexpected Error"_ns);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace mozilla::uniffi
|
|
|
|
#endif // mozilla_ScaffoldingCall_h
|