ladybird/Libraries/LibWebView/BrowserProcess.cpp
Aliaksandr Kalenik 4ea4d63008 Everywhere: Replace Unix socket IPC transport with Mach ports on macOS
On macOS, use Mach port messaging instead of Unix domain sockets for
all IPC transport. This makes the transport capable of carrying Mach
port rights as message attachments, which is a prerequisite for sending
IOSurface handles over the main IPC channel (currently sent via a
separate out-of-band path). It also avoids the need for the FD
acknowledgement protocol that TransportSocket requires, since Mach port
right transfers are atomic in the kernel.

Three connection establishment patterns:

- Spawned helper processes (WebContent, RequestServer, etc.) use the
  existing MachPortServer: the child sends its task port with a reply
  port, and the parent responds with a pre-created port pair.

- Socket-bootstrapped connections (WebDriver, BrowserProcess) exchange
  Mach port names over the socket, then drop the socket.

- Pre-created pairs for IPC tests and in-message transport transfer.

Attachment on macOS now wraps a MachPort instead of a file descriptor,
converting between the two via fileport_makeport()/fileport_makefd().

The LibIPC socket transport tests are disabled on macOS since they are
socket-specific.
2026-03-23 18:50:48 +01:00

148 lines
4.8 KiB
C++

/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <LibCore/Process.h>
#include <LibCore/Socket.h>
#include <LibCore/System.h>
#include <LibIPC/ConnectionToServer.h>
#include <LibIPC/Transport.h>
#include <LibWebView/Application.h>
#include <LibWebView/BrowserProcess.h>
#include <LibWebView/URL.h>
namespace WebView {
static HashMap<int, RefPtr<UIProcessConnectionFromClient>> s_connections;
class UIProcessClient final
: public IPC::ConnectionToServer<UIProcessClientEndpoint, UIProcessServerEndpoint> {
C_OBJECT(UIProcessClient);
private:
explicit UIProcessClient(NonnullOwnPtr<IPC::Transport>);
};
ErrorOr<BrowserProcess::ProcessDisposition> BrowserProcess::connect(Vector<ByteString> const& raw_urls, NewWindow new_window)
{
static constexpr auto process_name = "Ladybird"sv;
auto [socket_path, pid_path] = TRY(Process::paths_for_process(process_name));
if (auto pid = TRY(Process::get_process_pid(process_name, pid_path)); pid.has_value()) {
TRY(connect_as_client(socket_path, raw_urls, new_window));
return ProcessDisposition::ExitProcess;
}
TRY(connect_as_server(socket_path));
m_pid_path = pid_path;
m_pid_file = TRY(Core::File::open(pid_path, Core::File::OpenMode::Write));
TRY(m_pid_file->write_until_depleted(ByteString::number(Core::System::getpid())));
return ProcessDisposition::ContinueMainProcess;
}
ErrorOr<void> BrowserProcess::connect_as_client(ByteString const& socket_path, Vector<ByteString> const& raw_urls, NewWindow new_window)
{
auto socket = TRY(Core::LocalSocket::connect(socket_path));
auto transport = TRY(IPC::Transport::from_socket(move(socket)));
auto client = UIProcessClient::construct(move(transport));
switch (new_window) {
case NewWindow::Yes:
if (!client->send_sync_but_allow_failure<Messages::UIProcessServer::CreateNewWindow>(raw_urls))
dbgln("Failed to send CreateNewWindow message to UIProcess");
return {};
case NewWindow::No:
if (!client->send_sync_but_allow_failure<Messages::UIProcessServer::CreateNewTab>(raw_urls))
dbgln("Failed to send CreateNewTab message to UIProcess");
return {};
}
VERIFY_NOT_REACHED();
}
ErrorOr<void> BrowserProcess::connect_as_server(ByteString const& socket_path)
{
auto socket_fd = TRY(Process::create_ipc_socket(socket_path));
m_socket_path = socket_path;
m_local_server = Core::LocalServer::construct();
TRY(m_local_server->take_over_fd(socket_fd));
m_local_server->on_accept = [this](auto client_socket) {
auto transport = IPC::Transport::from_socket(move(client_socket));
if (transport.is_error()) {
dbgln("Failed to create IPC transport for UIProcess client: {}", transport.error());
return;
}
accept_transport(transport.release_value());
};
return {};
}
void BrowserProcess::accept_transport(NonnullOwnPtr<IPC::Transport> transport)
{
auto client = UIProcessConnectionFromClient::construct(move(transport), ++m_next_client_id);
client->on_new_tab = [this](auto raw_urls) {
if (this->on_new_tab)
this->on_new_tab(raw_urls);
};
client->on_new_window = [this](auto raw_urls) {
if (this->on_new_window)
this->on_new_window(raw_urls);
};
}
BrowserProcess::~BrowserProcess()
{
if (m_pid_file) {
MUST(m_pid_file->truncate(0));
#if defined(AK_OS_WINDOWS)
// NOTE: On Windows, System::open() duplicates the underlying OS file handle,
// so we need to explicitly close said handle, otherwise the unlink() call fails due
// to permission errors and we crash on shutdown.
m_pid_file->close();
#endif
MUST(Core::System::unlink(m_pid_path));
}
if (!m_socket_path.is_empty())
MUST(Core::System::unlink(m_socket_path));
}
UIProcessClient::UIProcessClient(NonnullOwnPtr<IPC::Transport> transport)
: IPC::ConnectionToServer<UIProcessClientEndpoint, UIProcessServerEndpoint>(*this, move(transport))
{
}
UIProcessConnectionFromClient::UIProcessConnectionFromClient(NonnullOwnPtr<IPC::Transport> transport, int client_id)
: IPC::ConnectionFromClient<UIProcessClientEndpoint, UIProcessServerEndpoint>(*this, move(transport), client_id)
{
s_connections.set(client_id, *this);
}
void UIProcessConnectionFromClient::die()
{
s_connections.remove(client_id());
}
void UIProcessConnectionFromClient::create_new_tab(Vector<ByteString> urls)
{
if (on_new_tab)
on_new_tab(sanitize_urls(urls, Application::settings().new_tab_page_url()));
}
void UIProcessConnectionFromClient::create_new_window(Vector<ByteString> urls)
{
if (on_new_window)
on_new_window(sanitize_urls(urls, Application::settings().new_tab_page_url()));
}
}