/* -*- 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 "nsPrintfCString.h" #include "nsString.h" #include "nsThreadUtils.h" #include "mozilla/dom/OwnedRustBuffer.h" #include "mozilla/dom/RootedDictionary.h" #include "mozilla/dom/UniFFIBinding.h" #include "mozilla/dom/UniFFICallbacks.h" #include "mozilla/Maybe.h" #include "mozilla/Logging.h" #include "mozilla/RefPtr.h" #include "mozilla/UniquePtr.h" static mozilla::LazyLogModule UNIFFI_INVOKE_CALLBACK_LOGGER("uniffi"); namespace mozilla::uniffi { using dom::ArrayBuffer; using dom::RootedDictionary; using dom::UniFFICallbackHandler; using dom::UniFFIScaffoldingCallCode; static Maybe GetCallbackInterfaceInfo( uint64_t aInterfaceId) { const Maybe cbiInfo = UniFFIGetCallbackInterfaceInfo(aInterfaceId); #ifdef MOZ_UNIFFI_FIXTURES if (!cbiInfo) { return UniFFIFixturesGetCallbackInterfaceInfo(aInterfaceId); } #endif return cbiInfo; } void RegisterCallbackHandler(uint64_t aInterfaceId, UniFFICallbackHandler& aCallbackHandler, ErrorResult& aError) { // We should only be mutating mHandler on the main thread MOZ_ASSERT(NS_IsMainThread()); Maybe cbiInfo = GetCallbackInterfaceInfo(aInterfaceId); if (!cbiInfo.isSome()) { aError.ThrowUnknownError( nsPrintfCString("Unknown interface id: %" PRIu64, aInterfaceId)); return; } if (*cbiInfo->mHandler) { MOZ_LOG(UNIFFI_INVOKE_CALLBACK_LOGGER, LogLevel::Error, ("[UniFFI] Callback handler already registered for %s", cbiInfo->mName)); return; } *cbiInfo->mHandler = &aCallbackHandler; RustCallStatus status = {0}; cbiInfo->mInitForeignCallback(cbiInfo->mForeignCallback, &status); // Just use MOZ_DIAGNOSTIC_ASSERT to check the status, since the call should // always succeed. The RustCallStatus out param only exists because UniFFI // adds it to all FFI calls. MOZ_DIAGNOSTIC_ASSERT(status.code == RUST_CALL_SUCCESS); } void DeregisterCallbackHandler(uint64_t aInterfaceId, ErrorResult& aError) { // We should only be mutating mHandler on the main thread MOZ_ASSERT(NS_IsMainThread()); Maybe cbiInfo = GetCallbackInterfaceInfo(aInterfaceId); if (!cbiInfo.isSome()) { aError.ThrowUnknownError( nsPrintfCString("Unknown interface id: %" PRIu64, aInterfaceId)); return; } *cbiInfo->mHandler = nullptr; } MOZ_CAN_RUN_SCRIPT static void QueueCallbackInner(uint64_t aInterfaceId, uint64_t aHandle, uint32_t aMethod, UniquePtr&& aArgsData, int32_t aArgsLen) { MOZ_ASSERT(NS_IsMainThread()); Maybe cbiInfo = GetCallbackInterfaceInfo(aInterfaceId); if (!cbiInfo.isSome()) { MOZ_LOG(UNIFFI_INVOKE_CALLBACK_LOGGER, LogLevel::Error, ("[UniFFI] Unknown inferface id: %" PRIu64, aInterfaceId)); return; } // Take our own reference to the callback handler to ensure that it stays // alive for the duration of this call RefPtr ihandler = *cbiInfo->mHandler; if (!ihandler) { MOZ_LOG(UNIFFI_INVOKE_CALLBACK_LOGGER, LogLevel::Error, ("[UniFFI] JS handler for %s not registered", cbiInfo->mName)); return; } JSObject* global = ihandler->CallbackGlobalOrNull(); if (!global) { MOZ_LOG(UNIFFI_INVOKE_CALLBACK_LOGGER, LogLevel::Error, ("[UniFFI] JS handler for %s has null global", cbiInfo->mName)); return; } dom::AutoEntryScript aes(global, cbiInfo->mName); JS::Rooted args( aes.cx(), JS::NewArrayBufferWithContents(aes.cx(), aArgsLen, std::move(aArgsData))); if (!args) { MOZ_LOG(UNIFFI_INVOKE_CALLBACK_LOGGER, LogLevel::Error, ("[UniFFI] Failed to allocate buffer for arguments")); return; } IgnoredErrorResult error; ihandler->Call(aHandle, aMethod, args, error); if (error.Failed()) { MOZ_LOG(UNIFFI_INVOKE_CALLBACK_LOGGER, LogLevel::Error, ("[UniFFI] Error invoking JS handler for %s", cbiInfo->mName)); return; } } void QueueCallback(uint64_t aInterfaceId, uint64_t aHandle, uint32_t aMethod, const uint8_t* aArgsData, int32_t aArgsLen) { // Make a copy of aArgsData to be deserialized asynchronously on // the main thread. // // This copy will be allocated in the ArrayBufferContentsArena, as // ownership will be passed to an ArrayBuffer for deserialization. UniquePtr argsDataOwned( js_pod_arena_malloc(js::ArrayBufferContentsArena, aArgsLen)); if (!argsDataOwned) { MOZ_LOG(UNIFFI_INVOKE_CALLBACK_LOGGER, LogLevel::Error, ("[UniFFI] Error allocating memory for arguments")); return; } memcpy(argsDataOwned.get(), aArgsData, aArgsLen); nsresult dispatchResult = GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( "UniFFI callback", [=, argsDataOwned = std::move(argsDataOwned)]() MOZ_CAN_RUN_SCRIPT_BOUNDARY mutable { QueueCallbackInner( aInterfaceId, aHandle, aMethod, std::move(argsDataOwned), aArgsLen); })); if (NS_FAILED(dispatchResult)) { MOZ_LOG(UNIFFI_INVOKE_CALLBACK_LOGGER, LogLevel::Error, ("[UniFFI] Error dispatching UniFFI callback task")); } } } // namespace mozilla::uniffi