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
 | 
