summaryrefslogtreecommitdiffstats
path: root/toolkit/components/uniffi-js/ScaffoldingCall.h
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/uniffi-js/ScaffoldingCall.h')
-rw-r--r--toolkit/components/uniffi-js/ScaffoldingCall.h260
1 files changed, 260 insertions, 0 deletions
diff --git a/toolkit/components/uniffi-js/ScaffoldingCall.h b/toolkit/components/uniffi-js/ScaffoldingCall.h
new file mode 100644
index 0000000000..02b016c513
--- /dev/null
+++ b/toolkit/components/uniffi-js/ScaffoldingCall.h
@@ -0,0 +1,260 @@
+/* -*- 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