/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "NativeMessagingPortal.h" #include #include #include "mozilla/ClearOnShutdown.h" #include "mozilla/GUniquePtr.h" #include "mozilla/Logging.h" #include "mozilla/UniquePtrExtensions.h" #include "mozilla/WidgetUtilsGtk.h" #include "mozilla/dom/Promise.h" #include "prlink.h" #include static mozilla::LazyLogModule gNativeMessagingPortalLog( "NativeMessagingPortal"); #ifdef MOZ_LOGGING # define LOG_NMP(...) \ MOZ_LOG(gNativeMessagingPortalLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) #else # define LOG_NMP(args) #endif #define GET_FUNC(func, lib) \ func##_fn = (decltype(func##_fn))PR_FindFunctionSymbol(lib, #func) static gint _g_unix_fd_list_get(GUnixFDList* list, gint index_, GError** error) { static PRLibrary* gioLib = nullptr; static bool gioInitialized = false; static gint (*g_unix_fd_list_get_fn)(GUnixFDList* list, gint index_, GError** error) = nullptr; if (!gioInitialized) { gioInitialized = true; gioLib = PR_LoadLibrary("libgio-2.0.so.0"); if (!gioLib) { return -1; } GET_FUNC(g_unix_fd_list_get, gioLib); } if (!g_unix_fd_list_get_fn) { return -1; } return g_unix_fd_list_get_fn(list, index_, error); } namespace mozilla::extensions { NS_IMPL_ISUPPORTS(NativeMessagingPortal, nsINativeMessagingPortal) /* static */ already_AddRefed NativeMessagingPortal::GetSingleton() { static StaticRefPtr sInstance; if (MOZ_UNLIKELY(!sInstance)) { sInstance = new NativeMessagingPortal(); ClearOnShutdown(&sInstance); } return do_AddRef(sInstance); } static void LogError(const char* aMethod, const GError& aError) { g_warning("%s error: %s", aMethod, aError.message); } static void RejectPromiseWithErrorMessage(dom::Promise& aPromise, const GError& aError) { aPromise.MaybeRejectWithOperationError(nsDependentCString(aError.message)); } static nsresult GetPromise(JSContext* aCx, RefPtr& aPromise) { nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx); if (NS_WARN_IF(!globalObject)) { return NS_ERROR_UNEXPECTED; } ErrorResult result; aPromise = dom::Promise::Create(globalObject, result); if (NS_WARN_IF(result.Failed())) { return result.StealNSResult(); } return NS_OK; } struct CallbackData { explicit CallbackData(dom::Promise& aPromise, const gchar* aSessionHandle = nullptr) : promise(&aPromise), sessionHandle(g_strdup(aSessionHandle)) {} RefPtr promise; GUniquePtr sessionHandle; guint subscription_id = 0; }; NativeMessagingPortal::NativeMessagingPortal() { LOG_NMP("NativeMessagingPortal::NativeMessagingPortal()"); mCancellable = dont_AddRef(g_cancellable_new()); g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.WebExtensions", mCancellable, &NativeMessagingPortal::OnProxyReady, this); } NativeMessagingPortal::~NativeMessagingPortal() { LOG_NMP("NativeMessagingPortal::~NativeMessagingPortal()"); g_cancellable_cancel(mCancellable); // Close all active sessions for (const auto& it : mSessions) { if (it.second != SessionState::Active) { continue; } GUniquePtr error; RefPtr proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync( G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr, "org.freedesktop.portal.Desktop", it.first.c_str(), "org.freedesktop.portal.Session", nullptr, getter_Transfers(error))); if (!proxy) { LOG_NMP("failed to get a D-Bus proxy: %s", error->message); LogError(__func__, *error); continue; } RefPtr res = dont_AddRef( g_dbus_proxy_call_sync(proxy, "Close", nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, getter_Transfers(error))); if (!res) { LOG_NMP("failed to close session: %s", error->message); LogError(__func__, *error); } } } NS_IMETHODIMP NativeMessagingPortal::ShouldUse(bool* aResult) { *aResult = widget::ShouldUsePortal(widget::PortalKind::NativeMessaging); LOG_NMP("will %sbe used", *aResult ? "" : "not "); return NS_OK; } struct NativeMessagingPortal::DelayedCall { using DelayedMethodCall = void (NativeMessagingPortal::*)(dom::Promise&, GVariant*); DelayedCall(DelayedMethodCall aCallback, dom::Promise& aPromise, GVariant* aArgs = nullptr) : callback(aCallback), promise(&aPromise), args(aArgs) { LOG_NMP("NativeMessagingPortal::DelayedCall::DelayedCall()"); } ~DelayedCall() { LOG_NMP("NativeMessagingPortal::DelayedCall::~DelayedCall()"); } DelayedMethodCall callback; RefPtr promise; RefPtr args; }; /* static */ void NativeMessagingPortal::OnProxyReady(GObject* source, GAsyncResult* result, gpointer user_data) { NativeMessagingPortal* self = static_cast(user_data); GUniquePtr error; self->mProxy = dont_AddRef( g_dbus_proxy_new_for_bus_finish(result, getter_Transfers(error))); if (self->mProxy) { LOG_NMP("D-Bus proxy ready for name %s, path %s, interface %s", g_dbus_proxy_get_name(self->mProxy), g_dbus_proxy_get_object_path(self->mProxy), g_dbus_proxy_get_interface_name(self->mProxy)); } else { LOG_NMP("failed to get a D-Bus proxy: %s", error->message); LogError(__func__, *error); } self->mInitialized = true; while (!self->mPending.empty()) { auto pending = std::move(self->mPending.front()); self->mPending.pop_front(); (self->*pending->callback)(*pending->promise, pending->args.get()); } } NS_IMETHODIMP NativeMessagingPortal::GetAvailable(JSContext* aCx, dom::Promise** aPromise) { RefPtr promise; MOZ_TRY(GetPromise(aCx, promise)); if (mInitialized) { MaybeDelayedIsAvailable(*promise, nullptr); } else { auto delayed = MakeUnique( &NativeMessagingPortal::MaybeDelayedIsAvailable, *promise); mPending.push_back(std::move(delayed)); } promise.forget(aPromise); return NS_OK; } void NativeMessagingPortal::MaybeDelayedIsAvailable(dom::Promise& aPromise, GVariant* aArgs) { MOZ_ASSERT(!aArgs); bool available = false; if (mProxy) { RefPtr version = dont_AddRef(g_dbus_proxy_get_cached_property(mProxy, "version")); if (version) { if (g_variant_get_uint32(version) >= 1) { available = true; } } } LOG_NMP("is %savailable", available ? "" : "not "); aPromise.MaybeResolve(available); } NS_IMETHODIMP NativeMessagingPortal::CreateSession(const nsACString& aApplication, JSContext* aCx, dom::Promise** aPromise) { RefPtr promise; MOZ_TRY(GetPromise(aCx, promise)); // Creating a session requires passing a unique token that will be used as the // suffix for the session handle, and it should be a valid D-Bus object path // component (i.e. it contains only the characters "[A-Z][a-z][0-9]_", see // https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path // and // https://flatpak.github.io/xdg-desktop-portal/#gdbus-org.freedesktop.portal.Session). // The token should be unique and not guessable. To avoid clashes with calls // made from unrelated libraries, it is a good idea to use a per-library // prefix combined with a random number. // Here, we build the token by concatenating MOZ_APP_NAME (e.g. "firefox"), // with the name of the native application (sanitized to remove invalid // characters, see // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#native_messaging_manifests), // and a random number. const nsCString& application = PromiseFlatCString(aApplication); GUniquePtr sanitizedApplicationName(g_strdup(application.get())); g_strdelimit(sanitizedApplicationName.get(), ".", '_'); GUniquePtr token(g_strdup_printf("%s_%s_%u", MOZ_APP_NAME, sanitizedApplicationName.get(), g_random_int())); RefPtr args = dont_AddRef(g_variant_new_string(token.get())); if (mInitialized) { MaybeDelayedCreateSession(*promise, args); } else { auto delayed = MakeUnique( &NativeMessagingPortal::MaybeDelayedCreateSession, *promise, args); mPending.push_back(std::move(delayed)); } promise.forget(aPromise); return NS_OK; } void NativeMessagingPortal::MaybeDelayedCreateSession(dom::Promise& aPromise, GVariant* aArgs) { MOZ_ASSERT(g_variant_is_of_type(aArgs, G_VARIANT_TYPE_STRING)); if (!mProxy) { return aPromise.MaybeRejectWithOperationError( "No D-Bus proxy for the native messaging portal"); } LOG_NMP("creating session with handle suffix %s", g_variant_get_string(aArgs, nullptr)); GVariantBuilder options; g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&options, "{sv}", "session_handle_token", g_variant_ref_sink(aArgs)); auto callbackData = MakeUnique(aPromise); g_dbus_proxy_call(mProxy, "CreateSession", g_variant_new("(a{sv})", &options), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &NativeMessagingPortal::OnCreateSessionDone, callbackData.release()); } /* static */ void NativeMessagingPortal::OnCreateSessionDone(GObject* source, GAsyncResult* result, gpointer user_data) { GDBusProxy* proxy = G_DBUS_PROXY(source); UniquePtr callbackData(static_cast(user_data)); GUniquePtr error; RefPtr res = dont_AddRef( g_dbus_proxy_call_finish(proxy, result, getter_Transfers(error))); if (res) { RefPtr sessionHandle = dont_AddRef(g_variant_get_child_value(res, 0)); gsize length; const char* value = g_variant_get_string(sessionHandle, &length); LOG_NMP("session created with handle %s", value); RefPtr portal = GetSingleton(); portal->mSessions[value] = SessionState::Active; GDBusConnection* connection = g_dbus_proxy_get_connection(proxy); // The "Closed" signal is emitted e.g. when the user denies access to the // native application when the shell prompts them. auto subscription_id_ptr = MakeUnique(0); *subscription_id_ptr = g_dbus_connection_signal_subscribe( connection, "org.freedesktop.portal.Desktop", "org.freedesktop.portal.Session", "Closed", value, nullptr, G_DBUS_SIGNAL_FLAGS_NONE, &NativeMessagingPortal::OnSessionClosedSignal, subscription_id_ptr.get(), [](gpointer aUserData) { UniquePtr release(reinterpret_cast(aUserData)); }); Unused << subscription_id_ptr.release(); // Ownership transferred above. callbackData->promise->MaybeResolve(nsDependentCString(value, length)); } else { LOG_NMP("failed to create session: %s", error->message); LogError(__func__, *error); RejectPromiseWithErrorMessage(*callbackData->promise, *error); } } NS_IMETHODIMP NativeMessagingPortal::CloseSession(const nsACString& aHandle, JSContext* aCx, dom::Promise** aPromise) { const nsCString& sessionHandle = PromiseFlatCString(aHandle); if (!g_variant_is_object_path(sessionHandle.get())) { LOG_NMP("cannot close session %s, invalid handle", sessionHandle.get()); return NS_ERROR_INVALID_ARG; } auto sessionIterator = mSessions.find(sessionHandle.get()); if (sessionIterator == mSessions.end()) { LOG_NMP("cannot close session %s, unknown handle", sessionHandle.get()); return NS_ERROR_INVALID_ARG; } if (sessionIterator->second != SessionState::Active) { LOG_NMP("cannot close session %s, not active", sessionHandle.get()); return NS_ERROR_FAILURE; } RefPtr promise; MOZ_TRY(GetPromise(aCx, promise)); sessionIterator->second = SessionState::Closing; LOG_NMP("closing session %s", sessionHandle.get()); auto callbackData = MakeUnique(*promise, sessionHandle.get()); g_dbus_proxy_new_for_bus( G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr, "org.freedesktop.portal.Desktop", sessionHandle.get(), "org.freedesktop.portal.Session", nullptr, &NativeMessagingPortal::OnCloseSessionProxyReady, callbackData.release()); promise.forget(aPromise); return NS_OK; } /* static */ void NativeMessagingPortal::OnCloseSessionProxyReady(GObject* source, GAsyncResult* result, gpointer user_data) { UniquePtr callbackData(static_cast(user_data)); GUniquePtr error; RefPtr proxy = dont_AddRef( g_dbus_proxy_new_for_bus_finish(result, getter_Transfers(error))); if (!proxy) { LOG_NMP("failed to close session: %s", error->message); LogError(__func__, *error); return RejectPromiseWithErrorMessage(*callbackData->promise, *error); } g_dbus_proxy_call(proxy, "Close", nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &NativeMessagingPortal::OnCloseSessionDone, callbackData.release()); } /* static */ void NativeMessagingPortal::OnCloseSessionDone(GObject* source, GAsyncResult* result, gpointer user_data) { GDBusProxy* proxy = G_DBUS_PROXY(source); UniquePtr callbackData(static_cast(user_data)); RefPtr portal = GetSingleton(); GUniquePtr error; RefPtr res = dont_AddRef( g_dbus_proxy_call_finish(proxy, result, getter_Transfers(error))); if (res) { LOG_NMP("session %s closed", callbackData->sessionHandle.get()); portal->mSessions.erase(callbackData->sessionHandle.get()); callbackData->promise->MaybeResolve(NS_OK); } else { LOG_NMP("failed to close session %s: %s", callbackData->sessionHandle.get(), error->message); LogError(__func__, *error); portal->mSessions[callbackData->sessionHandle.get()] = SessionState::Error; RejectPromiseWithErrorMessage(*callbackData->promise, *error); } } /* static */ void NativeMessagingPortal::OnSessionClosedSignal( GDBusConnection* bus, const gchar* sender_name, const gchar* object_path, const gchar* interface_name, const gchar* signal_name, GVariant* parameters, gpointer user_data) { guint subscription_id = *reinterpret_cast(user_data); LOG_NMP("session %s was closed by the portal", object_path); g_dbus_connection_signal_unsubscribe(bus, subscription_id); RefPtr portal = GetSingleton(); portal->mSessions.erase(object_path); } NS_IMETHODIMP NativeMessagingPortal::GetManifest(const nsACString& aHandle, const nsACString& aName, const nsACString& aExtension, JSContext* aCx, dom::Promise** aPromise) { const nsCString& sessionHandle = PromiseFlatCString(aHandle); const nsCString& name = PromiseFlatCString(aName); const nsCString& extension = PromiseFlatCString(aExtension); if (!g_variant_is_object_path(sessionHandle.get())) { LOG_NMP("cannot find manifest for %s, invalid session handle %s", name.get(), sessionHandle.get()); return NS_ERROR_INVALID_ARG; } auto sessionIterator = mSessions.find(sessionHandle.get()); if (sessionIterator == mSessions.end()) { LOG_NMP("cannot find manifest for %s, unknown session handle %s", name.get(), sessionHandle.get()); return NS_ERROR_INVALID_ARG; } if (sessionIterator->second != SessionState::Active) { LOG_NMP("cannot find manifest for %s, inactive session %s", name.get(), sessionHandle.get()); return NS_ERROR_FAILURE; } if (!mProxy) { LOG_NMP("cannot find manifest for %s, missing D-Bus proxy", name.get()); return NS_ERROR_FAILURE; } RefPtr promise; MOZ_TRY(GetPromise(aCx, promise)); auto callbackData = MakeUnique(*promise, sessionHandle.get()); g_dbus_proxy_call( mProxy, "GetManifest", g_variant_new("(oss)", sessionHandle.get(), name.get(), extension.get()), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &NativeMessagingPortal::OnGetManifestDone, callbackData.release()); promise.forget(aPromise); return NS_OK; } /* static */ void NativeMessagingPortal::OnGetManifestDone(GObject* source, GAsyncResult* result, gpointer user_data) { GDBusProxy* proxy = G_DBUS_PROXY(source); UniquePtr callbackData(static_cast(user_data)); GUniquePtr error; RefPtr jsonManifest = dont_AddRef( g_dbus_proxy_call_finish(proxy, result, getter_Transfers(error))); if (jsonManifest) { jsonManifest = dont_AddRef(g_variant_get_child_value(jsonManifest, 0)); gsize length; const char* value = g_variant_get_string(jsonManifest, &length); LOG_NMP("manifest found in session %s: %s", callbackData->sessionHandle.get(), value); callbackData->promise->MaybeResolve(nsDependentCString(value, length)); } else { LOG_NMP("failed to find a manifest in session %s: %s", callbackData->sessionHandle.get(), error->message); LogError(__func__, *error); RejectPromiseWithErrorMessage(*callbackData->promise, *error); } } NS_IMETHODIMP NativeMessagingPortal::Start(const nsACString& aHandle, const nsACString& aName, const nsACString& aExtension, JSContext* aCx, dom::Promise** aPromise) { const nsCString& sessionHandle = PromiseFlatCString(aHandle); const nsCString& name = PromiseFlatCString(aName); const nsCString& extension = PromiseFlatCString(aExtension); if (!g_variant_is_object_path(sessionHandle.get())) { LOG_NMP("cannot start %s, invalid session handle %s", name.get(), sessionHandle.get()); return NS_ERROR_INVALID_ARG; } auto sessionIterator = mSessions.find(sessionHandle.get()); if (sessionIterator == mSessions.end()) { LOG_NMP("cannot start %s, unknown session handle %s", name.get(), sessionHandle.get()); return NS_ERROR_INVALID_ARG; } if (sessionIterator->second != SessionState::Active) { LOG_NMP("cannot start %s, inactive session %s", name.get(), sessionHandle.get()); return NS_ERROR_FAILURE; } if (!mProxy) { LOG_NMP("cannot start %s, missing D-Bus proxy", name.get()); return NS_ERROR_FAILURE; } RefPtr promise; MOZ_TRY(GetPromise(aCx, promise)); auto callbackData = MakeUnique(*promise, sessionHandle.get()); auto* releasedCallbackData = callbackData.release(); LOG_NMP("starting %s, requested by %s in session %s", name.get(), extension.get(), sessionHandle.get()); GDBusConnection* connection = g_dbus_proxy_get_connection(mProxy); GUniquePtr senderName( g_strdup(g_dbus_connection_get_unique_name(connection))); g_strdelimit(senderName.get(), ".", '_'); GUniquePtr handleToken( g_strdup_printf("%s/%d", MOZ_APP_NAME, g_random_int_range(0, G_MAXINT))); GUniquePtr requestPath( g_strdup_printf("/org/freedesktop/portal/desktop/request/%s/%s", senderName.get() + 1, handleToken.get())); releasedCallbackData->subscription_id = g_dbus_connection_signal_subscribe( connection, "org.freedesktop.portal.Desktop", "org.freedesktop.portal.Request", "Response", requestPath.get(), nullptr, G_DBUS_SIGNAL_FLAGS_NONE, &NativeMessagingPortal::OnStartRequestResponseSignal, releasedCallbackData, nullptr); auto callbackDataCopy = MakeUnique(*promise, sessionHandle.get()); GVariantBuilder options; g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&options, "{sv}", "handle_token", g_variant_new_string(handleToken.get())); g_dbus_proxy_call(mProxy, "Start", g_variant_new("(ossa{sv})", sessionHandle.get(), name.get(), extension.get(), &options), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &NativeMessagingPortal::OnStartDone, callbackDataCopy.release()); promise.forget(aPromise); return NS_OK; } /* static */ void NativeMessagingPortal::OnStartDone(GObject* source, GAsyncResult* result, gpointer user_data) { GDBusProxy* proxy = G_DBUS_PROXY(source); UniquePtr callbackData(static_cast(user_data)); GUniquePtr error; RefPtr handle = dont_AddRef( g_dbus_proxy_call_finish(proxy, result, getter_Transfers(error))); if (handle) { handle = dont_AddRef(g_variant_get_child_value(handle, 0)); LOG_NMP( "native application start requested in session %s, pending response " "for %s", callbackData->sessionHandle.get(), g_variant_get_string(handle, nullptr)); } else { LOG_NMP("failed to start native application in session %s: %s", callbackData->sessionHandle.get(), error->message); LogError(__func__, *error); RejectPromiseWithErrorMessage(*callbackData->promise, *error); } } /* static */ void NativeMessagingPortal::OnStartRequestResponseSignal( GDBusConnection* bus, const gchar* sender_name, const gchar* object_path, const gchar* interface_name, const gchar* signal_name, GVariant* parameters, gpointer user_data) { UniquePtr callbackData(static_cast(user_data)); LOG_NMP("got response signal for %s in session %s", object_path, callbackData->sessionHandle.get()); g_dbus_connection_signal_unsubscribe(bus, callbackData->subscription_id); RefPtr result = dont_AddRef(g_variant_get_child_value(parameters, 0)); guint32 response = g_variant_get_uint32(result); // Possible values for response // (https://flatpak.github.io/xdg-desktop-portal/#gdbus-signal-org-freedesktop-portal-Request.Response): // 0: Success, the request is carried out // 1: The user cancelled the interaction // 2: The user interaction was ended in some other way if (response == 0) { LOG_NMP( "native application start successful in session %s, requesting file " "descriptors", callbackData->sessionHandle.get()); RefPtr portal = GetSingleton(); GVariantBuilder options; g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT); g_dbus_proxy_call_with_unix_fd_list( portal->mProxy.get(), "GetPipes", g_variant_new("(oa{sv})", callbackData->sessionHandle.get(), &options), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr, &NativeMessagingPortal::OnGetPipesDone, callbackData.release()); } else if (response == 1) { LOG_NMP("native application start canceled by user in session %s", callbackData->sessionHandle.get()); callbackData->promise->MaybeRejectWithAbortError( "Native application start canceled by user"); } else { LOG_NMP("native application start failed in session %s", callbackData->sessionHandle.get()); callbackData->promise->MaybeRejectWithNotFoundError( "Native application start failed"); } } static gint GetFD(const RefPtr& result, GUnixFDList* fds, gint index) { RefPtr value = dont_AddRef(g_variant_get_child_value(result, index)); GUniquePtr error; gint fd = _g_unix_fd_list_get(fds, g_variant_get_handle(value), getter_Transfers(error)); if (fd == -1) { LOG_NMP("failed to get file descriptor at index %d: %s", index, error->message); LogError("GetFD", *error); } return fd; } /* static */ void NativeMessagingPortal::OnGetPipesDone(GObject* source, GAsyncResult* result, gpointer user_data) { GDBusProxy* proxy = G_DBUS_PROXY(source); UniquePtr callbackData(static_cast(user_data)); auto promise = callbackData->promise; RefPtr fds; GUniquePtr error; RefPtr pipes = dont_AddRef(g_dbus_proxy_call_with_unix_fd_list_finish( proxy, getter_AddRefs(fds), result, getter_Transfers(error))); if (!pipes) { LOG_NMP( "failed to get file descriptors for native application in session %s: " "%s", callbackData->sessionHandle.get(), error->message); LogError(__func__, *error); return RejectPromiseWithErrorMessage(*promise, *error); } gint32 _stdin = GetFD(pipes, fds, 0); gint32 _stdout = GetFD(pipes, fds, 1); gint32 _stderr = GetFD(pipes, fds, 2); LOG_NMP( "got file descriptors for native application in session %s: (%d, %d, %d)", callbackData->sessionHandle.get(), _stdin, _stdout, _stderr); if (_stdin == -1 || _stdout == -1 || _stderr == -1) { return promise->MaybeRejectWithOperationError("Invalid file descriptor"); } dom::AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) { return promise->MaybeRejectWithUnknownError( "Failed to initialize JS context"); } JSContext* cx = jsapi.cx(); JS::Rooted jsPipes(cx, JS_NewPlainObject(cx)); if (!jsPipes) { return promise->MaybeRejectWithOperationError( "Failed to create a JS object to hold the file descriptors"); } auto setPipeProperty = [&](const char* name, int32_t value) { JS::Rooted jsValue(cx, JS::Value::fromInt32(value)); return JS_SetProperty(cx, jsPipes, name, jsValue); }; if (!setPipeProperty("stdin", _stdin)) { return promise->MaybeRejectWithOperationError( "Failed to set the 'stdin' property on the JS object"); } if (!setPipeProperty("stdout", _stdout)) { return promise->MaybeRejectWithOperationError( "Failed to set the 'stdout' property on the JS object"); } if (!setPipeProperty("stderr", _stderr)) { return promise->MaybeRejectWithOperationError( "Failed to set the 'stderr' property on the JS object"); } promise->MaybeResolve(jsPipes); } } // namespace mozilla::extensions