/* -*- 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/. */ #include "nsThreadUtils.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/RootedDictionary.h" #include "mozilla/uniffi/Call.h" #include "mozilla/uniffi/ResultPromise.h" namespace mozilla::uniffi { extern mozilla::LazyLogModule gUniffiLogger; using dom::GlobalObject; using dom::OwningUniFFIScaffoldingValue; using dom::RootedDictionary; using dom::Sequence; using dom::UniFFIScaffoldingCallResult; void UniffiSyncCallHandler::CallSync( UniquePtr aHandler, const GlobalObject& aGlobal, const Sequence& aArgs, RootedDictionary& aReturnValue, ErrorResult& aError) { MOZ_ASSERT(NS_IsMainThread()); aHandler->LowerRustArgs(aArgs, aError); if (aError.Failed()) { return; } RustCallStatus callStatus{}; aHandler->MakeRustCall(&callStatus); aHandler->mUniffiCallStatusCode = callStatus.code; if (callStatus.error_buf.data) { aHandler->mUniffiCallStatusErrorBuf = FfiValueRustBuffer(callStatus.error_buf); } aHandler->LiftCallResult(aGlobal.Context(), aReturnValue, aError); } already_AddRefed UniffiSyncCallHandler::CallAsyncWrapper( UniquePtr aHandler, const dom::GlobalObject& aGlobal, const dom::Sequence& aArgs, ErrorResult& aError) { MOZ_ASSERT(NS_IsMainThread()); aHandler->LowerRustArgs(aArgs, aError); if (aError.Failed()) { return nullptr; } uniffi::ResultPromise resultPromise; resultPromise.Init(aGlobal, aError); if (aError.Failed()) { return nullptr; } nsresult dispatchResult = NS_DispatchBackgroundTask( NS_NewRunnableFunction("UniffiSyncCallHandler::CallAsyncWrapper", [handler = std::move(aHandler), resultPromise = resultPromise]() mutable { RustCallStatus callStatus{}; handler->MakeRustCall(&callStatus); handler->mUniffiCallStatusCode = callStatus.code; if (callStatus.error_buf.data) { handler->mUniffiCallStatusErrorBuf = FfiValueRustBuffer(callStatus.error_buf); } resultPromise.Complete(std::move(handler)); }), NS_DISPATCH_EVENT_MAY_BLOCK); if (NS_FAILED(dispatchResult)) { MOZ_LOG(gUniffiLogger, LogLevel::Error, ("[UniFFI] NS_DispatchBackgroundTask failed")); resultPromise.RejectWithUnexpectedError(); } return do_AddRef(resultPromise.GetPromise()); } void UniffiCallHandlerBase::LiftCallResult( JSContext* aCx, dom::RootedDictionary& aDest, ErrorResult& aError) { switch (mUniffiCallStatusCode) { case RUST_CALL_SUCCESS: { aDest.mCode = dom::UniFFIScaffoldingCallCode::Success; LiftSuccessfulCallResult(aCx, aDest.mData, aError); break; } case RUST_CALL_ERROR: { // Rust Err() value. Populate data with the `RustBuffer` containing the // error aDest.mCode = dom::UniFFIScaffoldingCallCode::Error; mUniffiCallStatusErrorBuf.Lift(aCx, &aDest.mData.Construct(), aError); break; } default: { // This indicates a RustError, which should rarely happen in practice. // The normal case is a Rust panic, but FF sets panic=abort. aDest.mCode = dom::UniFFIScaffoldingCallCode::Internal_error; if (mUniffiCallStatusErrorBuf.IsSet()) { mUniffiCallStatusErrorBuf.Lift(aCx, &aDest.mData.Construct(), aError); } break; } } } already_AddRefed UniffiAsyncCallHandler::CallAsync( UniquePtr aHandler, const dom::GlobalObject& aGlobal, const dom::Sequence& aArgs, ErrorResult& aError) { MOZ_ASSERT(NS_IsMainThread()); // Async calls return a Future rather than doing any work. This means we can // make the call right now on the JS main thread without fear of blocking it. aHandler->LowerArgsAndMakeRustCall(aArgs, aError); if (aError.Failed()) { return nullptr; } aHandler->mPromise.Init(aGlobal, aError); if (aError.Failed()) { return nullptr; } ResultPromise returnPromise(aHandler->mPromise); // Schedule a poll for the future in a background thread. nsresult dispatchResult = NS_DispatchBackgroundTask( NS_NewRunnableFunction("UniffiAsyncCallHandler::CallAsync", [handler = std::move(aHandler)]() mutable { UniffiAsyncCallHandler::Poll(std::move(handler)); })); if (NS_FAILED(dispatchResult)) { MOZ_LOG(gUniffiLogger, LogLevel::Error, ("[UniFFI] NS_DispatchBackgroundTask failed")); aHandler->mPromise.RejectWithUnexpectedError(); } return do_AddRef(returnPromise.GetPromise()); } // Callback function for async calls // // This is passed to Rust when we poll the future alongside a 64-bit handle that // represents the callback data. For uniffi-bindgen-gecko-js, the handle is a // `UniffiAsyncCallHandler*` casted to an int. // // Rust calls this when either the future is ready or when it's time to poll it // again. void UniffiAsyncCallHandler::FutureCallback(uint64_t aCallHandlerHandle, int8_t aCode) { // Recreate the UniquePtr we previously released UniquePtr handler( reinterpret_cast( static_cast(aCallHandlerHandle))); switch (aCode) { case UNIFFI_FUTURE_READY: { // `Future::poll` returned a `Ready` value on the Rust side. // Complete the future RustCallStatus callStatus{}; handler->CallCompleteFn(&callStatus); handler->mUniffiCallStatusCode = callStatus.code; if (callStatus.error_buf.data) { handler->mUniffiCallStatusErrorBuf = FfiValueRustBuffer(callStatus.error_buf); } // Copy of the ResultPromise before moving the handler that contains it. ResultPromise promise(handler->mPromise); promise.Complete(std::move(handler)); break; } case UNIFFI_FUTURE_MAYBE_READY: { // The Rust waker was invoked after `poll` returned a `Pending` value. // Poll the future again soon in a background task. nsresult dispatchResult = NS_DispatchBackgroundTask(NS_NewRunnableFunction( "UniffiAsyncCallHandler::FutureCallback", [handler = std::move(handler)]() mutable { UniffiAsyncCallHandler::Poll(std::move(handler)); })); if (NS_FAILED(dispatchResult)) { MOZ_LOG(gUniffiLogger, LogLevel::Error, ("[UniFFI] NS_DispatchBackgroundTask failed")); handler->mPromise.RejectWithUnexpectedError(); } break; } default: { // Invalid poll code, this should never happen, but if it does log an // error and reject the promise. MOZ_LOG(gUniffiLogger, LogLevel::Error, ("[UniFFI] Invalid poll code in " "UniffiAsyncCallHandler::FutureCallback %d", aCode)); handler->mPromise.RejectWithUnexpectedError(); break; } }; } void UniffiAsyncCallHandler::Poll(UniquePtr aHandler) { auto futureHandle = aHandler->mFutureHandle; auto pollFn = aHandler->mPollFn; // Release the UniquePtr into a raw pointer and convert it to a handle // so that we can pass it as a handle to the UniFFI code. It gets converted // back in `UniffiAsyncCallHandler::FutureCallback()`, which the Rust code // guarentees will be called if the future makes progress. uint64_t selfHandle = static_cast(reinterpret_cast(aHandler.release())); pollFn(futureHandle, UniffiAsyncCallHandler::FutureCallback, selfHandle); } UniffiAsyncCallHandler::~UniffiAsyncCallHandler() { mFreeFn(mFutureHandle); } } // namespace mozilla::uniffi