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
 |