summaryrefslogtreecommitdiffstats
path: root/toolkit/components/uniffi-js/UniFFICallbacks.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/uniffi-js/UniFFICallbacks.cpp')
-rw-r--r--toolkit/components/uniffi-js/UniFFICallbacks.cpp164
1 files changed, 164 insertions, 0 deletions
diff --git a/toolkit/components/uniffi-js/UniFFICallbacks.cpp b/toolkit/components/uniffi-js/UniFFICallbacks.cpp
new file mode 100644
index 0000000000..647914d73a
--- /dev/null
+++ b/toolkit/components/uniffi-js/UniFFICallbacks.cpp
@@ -0,0 +1,164 @@
+/* -*- 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<CallbackInterfaceInfo> GetCallbackInterfaceInfo(
+ uint64_t aInterfaceId) {
+ const Maybe<CallbackInterfaceInfo> 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<CallbackInterfaceInfo> 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<CallbackInterfaceInfo> 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<uint8_t[], JS::FreePolicy>&& aArgsData,
+ int32_t aArgsLen) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Maybe<CallbackInterfaceInfo> 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<UniFFICallbackHandler> 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<JSObject*> 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<uint8_t[], JS::FreePolicy> argsDataOwned(
+ js_pod_arena_malloc<uint8_t>(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