Everywhere: Remove dynamic Mach bootstrap registration on macOS
Registering multiple Mach port names with the bootstrap server at runtime is not how macOS expects it to be used — the bootstrap server is meant for static services, and the only reason we used it originally was so child processes could reach back to the UI process. Remove bootstrap_transport_over_socket(), which had both sides register dynamic names with the bootstrap server and exchange them over a socket. Instead, WebDriver and BrowserProcess connections now go through MachPortServer instances directly. When a non-child process contacts a MachPortServer, the server creates a port pair on demand (detected via sysctl ppid check) and returns the local half immediately. This keeps bootstrap server usage limited to the one original case: child processes looking up their parent's MachPortServer. WebDriver Session now runs its own MachPortServer per session. --webdriver-content-path becomes --webdriver-mach-server-name on macOS. Spare WebContent launches are skipped when a WebDriver session is active to avoid bootstrap races.
This commit is contained in:
parent
4ea4d63008
commit
c6d740ea41
24 changed files with 353 additions and 194 deletions
|
|
@ -5,39 +5,18 @@
|
|||
*/
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <LibCore/MachPort.h>
|
||||
#include <LibCore/Platform/MachMessageTypes.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibIPC/TransportBootstrapMach.h>
|
||||
|
||||
#include <mach/mach.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
static ErrorOr<void> write_exact(int fd, ReadonlyBytes bytes)
|
||||
{
|
||||
size_t total_written = 0;
|
||||
while (total_written < bytes.size()) {
|
||||
auto nwritten = TRY(Core::System::write(fd, bytes.slice(total_written)));
|
||||
VERIFY(nwritten > 0);
|
||||
total_written += nwritten;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<void> read_exact(int fd, Bytes bytes)
|
||||
{
|
||||
size_t total_read = 0;
|
||||
while (total_read < bytes.size()) {
|
||||
auto nread = TRY(Core::System::read(fd, bytes.slice(total_read)));
|
||||
VERIFY(nread > 0);
|
||||
total_read += nread;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_from_server_port(Core::MachPort const& server_port)
|
||||
{
|
||||
auto reply_port = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
|
|
@ -84,42 +63,6 @@ ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_from_mach_server(String
|
|||
return bootstrap_transport_from_server_port(server_port);
|
||||
}
|
||||
|
||||
ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_over_socket(Core::LocalSocket& socket)
|
||||
{
|
||||
auto our_receive_right = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
auto our_name = ByteString::formatted("org.ladybird.ipc.{}.{}", Core::System::getpid(), our_receive_right.port());
|
||||
TRY(our_receive_right.register_with_bootstrap_server(our_name));
|
||||
|
||||
auto socket_fd = socket.fd().value();
|
||||
TRY(socket.set_blocking(true));
|
||||
|
||||
auto const name_bytes = our_name.bytes();
|
||||
u32 const name_length = static_cast<u32>(name_bytes.size());
|
||||
TRY(write_exact(socket_fd, ReadonlyBytes { reinterpret_cast<u8 const*>(&name_length), sizeof(name_length) }));
|
||||
TRY(write_exact(socket_fd, name_bytes));
|
||||
|
||||
u32 peer_name_length = 0;
|
||||
TRY(read_exact(socket_fd, Bytes { reinterpret_cast<u8*>(&peer_name_length), sizeof(peer_name_length) }));
|
||||
VERIFY(peer_name_length > 0);
|
||||
VERIFY(peer_name_length <= 256);
|
||||
|
||||
auto peer_name_buffer = TRY(ByteBuffer::create_uninitialized(peer_name_length));
|
||||
TRY(read_exact(socket_fd, peer_name_buffer.bytes()));
|
||||
|
||||
auto peer_name = ByteString { peer_name_buffer.bytes() };
|
||||
auto peer_send_right = TRY(Core::MachPort::look_up_from_bootstrap_server(peer_name));
|
||||
|
||||
u8 ack = 1;
|
||||
TRY(write_exact(socket_fd, { &ack, 1 }));
|
||||
TRY(read_exact(socket_fd, { &ack, 1 }));
|
||||
VERIFY(ack == 1);
|
||||
|
||||
return TransportBootstrapMachPorts {
|
||||
.receive_right = move(our_receive_right),
|
||||
.send_right = move(peer_send_right),
|
||||
};
|
||||
}
|
||||
|
||||
void TransportBootstrapMachServer::send_transport_ports_to_child(Core::MachPort reply_port, TransportBootstrapMachPorts ports)
|
||||
{
|
||||
Core::Platform::MessageWithIPCChannelPorts message {};
|
||||
|
|
@ -141,17 +84,57 @@ void TransportBootstrapMachServer::send_transport_ports_to_child(Core::MachPort
|
|||
VERIFY(ret == KERN_SUCCESS);
|
||||
}
|
||||
|
||||
static bool process_is_child_of_us(pid_t pid)
|
||||
{
|
||||
int mib[4] = {};
|
||||
struct kinfo_proc info = {};
|
||||
size_t size = sizeof(info);
|
||||
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PID;
|
||||
mib[3] = pid;
|
||||
|
||||
if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) < 0)
|
||||
return false;
|
||||
|
||||
if (size == 0)
|
||||
return false;
|
||||
|
||||
return info.kp_eproc.e_ppid == Core::System::getpid();
|
||||
}
|
||||
|
||||
ErrorOr<TransportBootstrapMachPorts> TransportBootstrapMachServer::create_on_demand_local_transport(Core::MachPort reply_port)
|
||||
{
|
||||
auto local_receive_right = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
auto local_send_right = TRY(local_receive_right.insert_right(Core::MachPort::MessageRight::MakeSend));
|
||||
|
||||
auto remote_receive_right = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
auto remote_send_right = TRY(remote_receive_right.insert_right(Core::MachPort::MessageRight::MakeSend));
|
||||
|
||||
send_transport_ports_to_child(move(reply_port), TransportBootstrapMachPorts {
|
||||
.receive_right = move(remote_receive_right),
|
||||
.send_right = move(local_send_right),
|
||||
});
|
||||
|
||||
return TransportBootstrapMachPorts {
|
||||
.receive_right = move(local_receive_right),
|
||||
.send_right = move(remote_send_right),
|
||||
};
|
||||
}
|
||||
|
||||
void TransportBootstrapMachServer::register_pending_transport(pid_t pid, TransportBootstrapMachPorts ports)
|
||||
{
|
||||
m_pending_bootstrap_mutex.lock();
|
||||
auto pending = m_pending_bootstrap.take(pid);
|
||||
if (!pending.has_value()) {
|
||||
m_pending_bootstrap.set(pid, WaitingForPorts { move(ports) });
|
||||
m_pending_bootstrap_mutex.unlock();
|
||||
return;
|
||||
Optional<PendingBootstrapState> pending;
|
||||
{
|
||||
Threading::MutexLocker locker(m_pending_bootstrap_mutex);
|
||||
pending = m_pending_bootstrap.take(pid);
|
||||
if (!pending.has_value()) {
|
||||
m_pending_bootstrap.set(pid, WaitingForPorts { move(ports) });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_pending_bootstrap_mutex.unlock();
|
||||
pending.release_value().visit(
|
||||
[&](WaitingForPorts&) {
|
||||
VERIFY_NOT_REACHED();
|
||||
|
|
@ -161,22 +144,29 @@ void TransportBootstrapMachServer::register_pending_transport(pid_t pid, Transpo
|
|||
});
|
||||
}
|
||||
|
||||
void TransportBootstrapMachServer::register_reply_port(pid_t pid, Core::MachPort reply_port)
|
||||
ErrorOr<TransportBootstrapMachServer::RegisterReplyPortResult> TransportBootstrapMachServer::register_reply_port(pid_t pid, Core::MachPort reply_port)
|
||||
{
|
||||
m_pending_bootstrap_mutex.lock();
|
||||
auto pending = m_pending_bootstrap.take(pid);
|
||||
if (!pending.has_value()) {
|
||||
m_pending_bootstrap.set(pid, WaitingForReplyPort { move(reply_port) });
|
||||
m_pending_bootstrap_mutex.unlock();
|
||||
return;
|
||||
Optional<PendingBootstrapState> pending;
|
||||
{
|
||||
Threading::MutexLocker locker(m_pending_bootstrap_mutex);
|
||||
pending = m_pending_bootstrap.take(pid);
|
||||
if (!pending.has_value()) {
|
||||
if (process_is_child_of_us(pid)) {
|
||||
m_pending_bootstrap.set(pid, WaitingForReplyPort { move(reply_port) });
|
||||
return WaitingForChildTransport {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_pending_bootstrap_mutex.unlock();
|
||||
pending.release_value().visit(
|
||||
[&](WaitingForPorts& waiting) {
|
||||
if (!pending.has_value())
|
||||
return OnDemandTransport { .ports = TRY(create_on_demand_local_transport(move(reply_port))) };
|
||||
|
||||
return pending.release_value().visit(
|
||||
[&](WaitingForPorts& waiting) -> ErrorOr<RegisterReplyPortResult> {
|
||||
send_transport_ports_to_child(move(reply_port), move(waiting.ports));
|
||||
return WaitingForChildTransport {};
|
||||
},
|
||||
[&](WaitingForReplyPort&) {
|
||||
[&](WaitingForReplyPort&) -> ErrorOr<RegisterReplyPortResult> {
|
||||
VERIFY_NOT_REACHED();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
#endif
|
||||
|
||||
#include <LibCore/MachPort.h>
|
||||
#include <LibCore/Socket.h>
|
||||
#include <LibThreading/Mutex.h>
|
||||
|
||||
namespace IPC {
|
||||
|
|
@ -28,7 +27,6 @@ struct TransportBootstrapMachPorts {
|
|||
|
||||
ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_from_mach_server(StringView server_name);
|
||||
ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_from_server_port(Core::MachPort const& server_port);
|
||||
ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_over_socket(Core::LocalSocket&);
|
||||
|
||||
class TransportBootstrapMachServer {
|
||||
AK_MAKE_NONCOPYABLE(TransportBootstrapMachServer);
|
||||
|
|
@ -36,8 +34,15 @@ class TransportBootstrapMachServer {
|
|||
public:
|
||||
TransportBootstrapMachServer() = default;
|
||||
|
||||
struct WaitingForChildTransport {
|
||||
};
|
||||
struct OnDemandTransport {
|
||||
TransportBootstrapMachPorts ports;
|
||||
};
|
||||
using RegisterReplyPortResult = Variant<WaitingForChildTransport, OnDemandTransport>;
|
||||
|
||||
void register_pending_transport(pid_t, TransportBootstrapMachPorts);
|
||||
void register_reply_port(pid_t, Core::MachPort reply_port);
|
||||
ErrorOr<RegisterReplyPortResult> register_reply_port(pid_t, Core::MachPort reply_port);
|
||||
|
||||
private:
|
||||
struct WaitingForPorts {
|
||||
|
|
@ -49,6 +54,7 @@ private:
|
|||
using PendingBootstrapState = Variant<WaitingForPorts, WaitingForReplyPort>;
|
||||
|
||||
static void send_transport_ports_to_child(Core::MachPort reply_port, TransportBootstrapMachPorts ports);
|
||||
static ErrorOr<TransportBootstrapMachPorts> create_on_demand_local_transport(Core::MachPort reply_port);
|
||||
|
||||
Threading::Mutex m_pending_bootstrap_mutex;
|
||||
HashMap<pid_t, PendingBootstrapState> m_pending_bootstrap;
|
||||
|
|
|
|||
|
|
@ -7,9 +7,7 @@
|
|||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <LibCore/MachPort.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/Socket.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibIPC/TransportBootstrapMach.h>
|
||||
#include <LibIPC/TransportMachPort.h>
|
||||
#include <LibThreading/Thread.h>
|
||||
|
||||
|
|
@ -48,12 +46,6 @@ static Attachment attachment_from_descriptor(mach_msg_port_descriptor_t const& d
|
|||
}
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<TransportMachPort>> TransportMachPort::from_socket(NonnullOwnPtr<Core::LocalSocket> socket)
|
||||
{
|
||||
auto ports = TRY(bootstrap_transport_over_socket(*socket));
|
||||
return make<TransportMachPort>(move(ports.receive_right), move(ports.send_right));
|
||||
}
|
||||
|
||||
ErrorOr<TransportMachPort::Paired> TransportMachPort::create_paired()
|
||||
{
|
||||
auto port_a_recv = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
|
|
|
|||
|
|
@ -37,11 +37,6 @@ public:
|
|||
};
|
||||
static ErrorOr<Paired> create_paired();
|
||||
|
||||
// Bootstrap a Mach port connection over an existing Unix socket.
|
||||
// Used when the initial connection is socket-based (e.g., WebDriver).
|
||||
// Both sides must call this concurrently on the same socket.
|
||||
static ErrorOr<NonnullOwnPtr<TransportMachPort>> from_socket(NonnullOwnPtr<Core::LocalSocket> socket);
|
||||
|
||||
TransportMachPort(Core::MachPort receive_right, Core::MachPort send_right);
|
||||
~TransportMachPort();
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include <LibWebView/WebContentClient.h>
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibIPC/Transport.h>
|
||||
# include <LibIPC/TransportBootstrapMach.h>
|
||||
# include <LibWebView/MachPortServer.h>
|
||||
#endif
|
||||
|
|
@ -88,12 +89,31 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
|||
#endif
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
m_mach_port_server = make<MachPortServer>();
|
||||
m_mach_port_server = make<MachPortServer>(mach_server_name_for_process("Ladybird"sv, Core::System::getpid()));
|
||||
set_mach_server_name(m_mach_port_server->server_port_name());
|
||||
|
||||
m_mach_port_server->on_receive_child_mach_port = [this](MachPortServer::ChildMachPortRegistration registration) {
|
||||
set_process_mach_port(registration.pid, move(registration.child_port));
|
||||
m_transport_bootstrap_server.register_reply_port(registration.pid, move(registration.reply_port));
|
||||
auto registration_result = m_transport_bootstrap_server.register_reply_port(registration.pid, move(registration.reply_port));
|
||||
if (registration_result.is_error()) {
|
||||
dbgln("Failed to bootstrap Mach transport for pid {}: {}", registration.pid, registration_result.error());
|
||||
return;
|
||||
}
|
||||
|
||||
registration_result.release_value().visit(
|
||||
[](IPC::TransportBootstrapMachServer::WaitingForChildTransport) {
|
||||
},
|
||||
[this](IPC::TransportBootstrapMachServer::OnDemandTransport& transport) {
|
||||
if (!m_on_browser_process_transport)
|
||||
return;
|
||||
|
||||
VERIFY(m_event_loop);
|
||||
m_event_loop->deferred_invoke([this, transport = move(transport.ports)]() mutable {
|
||||
if (!m_on_browser_process_transport)
|
||||
return;
|
||||
m_on_browser_process_transport(make<IPC::Transport>(move(transport.receive_right), move(transport.send_right)));
|
||||
});
|
||||
});
|
||||
};
|
||||
m_mach_port_server->on_receive_backing_stores = [](MachPortServer::BackingStoresMessage message) {
|
||||
if (auto view = WebContentClient::view_for_pid_and_page_id(message.pid, message.page_id); view.has_value())
|
||||
|
|
@ -114,7 +134,7 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
|||
Optional<u16> devtools_port;
|
||||
Optional<StringView> debug_process;
|
||||
Optional<StringView> profile_process;
|
||||
Optional<StringView> webdriver_content_ipc_path;
|
||||
Optional<StringView> webdriver_endpoint;
|
||||
Optional<StringView> user_agent_preset;
|
||||
Optional<StringView> dns_server_address;
|
||||
Optional<StringView> default_time_zone;
|
||||
|
|
@ -173,7 +193,11 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
|||
args_parser.add_option(disable_sql_database, "Disable SQL database", "disable-sql-database");
|
||||
args_parser.add_option(debug_process, "Wait for a debugger to attach to the given process name (WebContent, RequestServer, etc.)", "debug-process", 0, "process-name");
|
||||
args_parser.add_option(profile_process, "Enable callgrind profiling of the given process name (WebContent, RequestServer, etc.)", "profile-process", 0, "process-name");
|
||||
args_parser.add_option(webdriver_content_ipc_path, "Path to WebDriver IPC for WebContent", "webdriver-content-path", 0, "path", Core::ArgsParser::OptionHideMode::CommandLineAndMarkdown);
|
||||
#if defined(AK_OS_MACOS)
|
||||
args_parser.add_option(webdriver_endpoint, "Mach server name for WebDriver IPC", "webdriver-mach-server-name", 0, "name", Core::ArgsParser::OptionHideMode::CommandLineAndMarkdown);
|
||||
#else
|
||||
args_parser.add_option(webdriver_endpoint, "Path to WebDriver IPC for WebContent", "webdriver-content-path", 0, "path", Core::ArgsParser::OptionHideMode::CommandLineAndMarkdown);
|
||||
#endif
|
||||
args_parser.add_option(enable_test_mode, "Enable test mode", "test-mode");
|
||||
args_parser.add_option(log_all_js_exceptions, "Log all JavaScript exceptions", "log-all-js-exceptions");
|
||||
args_parser.add_option(disable_site_isolation, "Disable site isolation", "disable-site-isolation");
|
||||
|
|
@ -269,8 +293,8 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
|||
if (window_height.has_value())
|
||||
m_browser_options.window_height = *window_height;
|
||||
|
||||
if (webdriver_content_ipc_path.has_value())
|
||||
m_browser_options.webdriver_content_ipc_path = *webdriver_content_ipc_path;
|
||||
if (webdriver_endpoint.has_value())
|
||||
m_browser_options.webdriver_endpoint = *webdriver_endpoint;
|
||||
|
||||
auto http_disk_cache_mode = HTTPDiskCacheMode::Enabled;
|
||||
if (disable_http_disk_cache)
|
||||
|
|
@ -357,6 +381,11 @@ ErrorOr<NonnullRefPtr<WebContentClient>> Application::launch_web_content_process
|
|||
|
||||
void Application::launch_spare_web_content_process()
|
||||
{
|
||||
// Spare WebContent processes inherit the active WebDriver endpoint, but they are not part of the
|
||||
// session and can race browser shutdown while bootstrapping.
|
||||
if (browser_options().webdriver_endpoint.has_value())
|
||||
return;
|
||||
|
||||
// Disable spare processes when debugging WebContent. Otherwise, it breaks running `gdb attach -p $(pidof WebContent)`.
|
||||
if (browser_options().debug_helper_process == ProcessType::WebContent)
|
||||
return;
|
||||
|
|
@ -571,7 +600,7 @@ ErrorOr<int> Application::execute()
|
|||
|
||||
view = HeadlessWebView::create(move(theme), { m_browser_options.window_width, m_browser_options.window_height });
|
||||
|
||||
if (!m_browser_options.webdriver_content_ipc_path.has_value()) {
|
||||
if (!m_browser_options.webdriver_endpoint.has_value()) {
|
||||
if (m_browser_options.urls.size() != 1)
|
||||
return Error::from_string_literal("Headless mode currently only supports exactly one URL");
|
||||
|
||||
|
|
@ -614,6 +643,13 @@ void Application::set_process_mach_port(pid_t pid, Core::MachPort&& port)
|
|||
}
|
||||
#endif
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
void Application::set_browser_process_transport_handler(Function<void(NonnullOwnPtr<IPC::Transport>)> handler)
|
||||
{
|
||||
m_on_browser_process_transport = move(handler);
|
||||
}
|
||||
#endif
|
||||
|
||||
Optional<Process&> Application::find_process(pid_t pid)
|
||||
{
|
||||
return m_process_manager->find_process(pid);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
|
|
@ -14,6 +15,7 @@
|
|||
#include <LibDatabase/Forward.h>
|
||||
#include <LibDevTools/DevToolsDelegate.h>
|
||||
#include <LibDevTools/Forward.h>
|
||||
#include <LibIPC/Forward.h>
|
||||
#include <LibImageDecoderClient/Client.h>
|
||||
#include <LibMain/Main.h>
|
||||
#include <LibRequests/Forward.h>
|
||||
|
|
@ -64,6 +66,7 @@ public:
|
|||
static ProcessManager& process_manager() { return *the().m_process_manager; }
|
||||
#if defined(AK_OS_MACOS)
|
||||
static IPC::TransportBootstrapMachServer& transport_bootstrap_server() { return the().m_transport_bootstrap_server; }
|
||||
void set_browser_process_transport_handler(Function<void(NonnullOwnPtr<IPC::Transport>)> handler);
|
||||
#endif
|
||||
|
||||
ErrorOr<NonnullRefPtr<WebContentClient>> launch_web_content_process(ViewImplementation&);
|
||||
|
|
@ -270,6 +273,7 @@ private:
|
|||
#if defined(AK_OS_MACOS)
|
||||
OwnPtr<MachPortServer> m_mach_port_server;
|
||||
IPC::TransportBootstrapMachServer m_transport_bootstrap_server;
|
||||
Function<void(NonnullOwnPtr<IPC::Transport>)> m_on_browser_process_transport;
|
||||
#endif
|
||||
|
||||
OwnPtr<DevTools::DevToolsServer> m_devtools;
|
||||
|
|
|
|||
|
|
@ -10,9 +10,13 @@
|
|||
#include <LibCore/System.h>
|
||||
#include <LibIPC/ConnectionToServer.h>
|
||||
#include <LibIPC/Transport.h>
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibIPC/TransportBootstrapMach.h>
|
||||
#endif
|
||||
#include <LibWebView/Application.h>
|
||||
#include <LibWebView/BrowserProcess.h>
|
||||
#include <LibWebView/URL.h>
|
||||
#include <LibWebView/Utilities.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
|
|
@ -33,11 +37,19 @@ ErrorOr<BrowserProcess::ProcessDisposition> BrowserProcess::connect(Vector<ByteS
|
|||
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()) {
|
||||
#if defined(AK_OS_MACOS)
|
||||
TRY(connect_as_client(*pid, raw_urls, new_window));
|
||||
#else
|
||||
TRY(connect_as_client(socket_path, raw_urls, new_window));
|
||||
#endif
|
||||
return ProcessDisposition::ExitProcess;
|
||||
}
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
TRY(connect_as_server());
|
||||
#else
|
||||
TRY(connect_as_server(socket_path));
|
||||
#endif
|
||||
|
||||
m_pid_path = pid_path;
|
||||
m_pid_file = TRY(Core::File::open(pid_path, Core::File::OpenMode::Write));
|
||||
|
|
@ -46,6 +58,26 @@ ErrorOr<BrowserProcess::ProcessDisposition> BrowserProcess::connect(Vector<ByteS
|
|||
return ProcessDisposition::ContinueMainProcess;
|
||||
}
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
ErrorOr<void> BrowserProcess::connect_as_client(pid_t pid, Vector<ByteString> const& raw_urls, NewWindow new_window)
|
||||
{
|
||||
auto transport_ports = TRY(IPC::bootstrap_transport_from_mach_server(mach_server_name_for_process("Ladybird"sv, pid)));
|
||||
auto client = UIProcessClient::construct(make<IPC::Transport>(move(transport_ports.receive_right), move(transport_ports.send_right)));
|
||||
|
||||
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();
|
||||
}
|
||||
#else
|
||||
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));
|
||||
|
|
@ -65,7 +97,17 @@ ErrorOr<void> BrowserProcess::connect_as_client(ByteString const& socket_path, V
|
|||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
ErrorOr<void> BrowserProcess::connect_as_server()
|
||||
{
|
||||
Application::the().set_browser_process_transport_handler([this](auto transport) {
|
||||
accept_transport(move(transport));
|
||||
});
|
||||
return {};
|
||||
}
|
||||
#else
|
||||
ErrorOr<void> BrowserProcess::connect_as_server(ByteString const& socket_path)
|
||||
{
|
||||
auto socket_fd = TRY(Process::create_ipc_socket(socket_path));
|
||||
|
|
@ -85,6 +127,7 @@ ErrorOr<void> BrowserProcess::connect_as_server(ByteString const& socket_path)
|
|||
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
|
||||
void BrowserProcess::accept_transport(NonnullOwnPtr<IPC::Transport> transport)
|
||||
{
|
||||
|
|
@ -102,6 +145,10 @@ void BrowserProcess::accept_transport(NonnullOwnPtr<IPC::Transport> transport)
|
|||
|
||||
BrowserProcess::~BrowserProcess()
|
||||
{
|
||||
#if defined(AK_OS_MACOS)
|
||||
Application::the().set_browser_process_transport_handler({});
|
||||
#endif
|
||||
|
||||
if (m_pid_file) {
|
||||
MUST(m_pid_file->truncate(0));
|
||||
#if defined(AK_OS_WINDOWS)
|
||||
|
|
|
|||
|
|
@ -60,8 +60,13 @@ public:
|
|||
|
||||
private:
|
||||
void accept_transport(NonnullOwnPtr<IPC::Transport>);
|
||||
#if defined(AK_OS_MACOS)
|
||||
ErrorOr<void> connect_as_client(pid_t pid, Vector<ByteString> const& raw_urls, NewWindow new_window);
|
||||
ErrorOr<void> connect_as_server();
|
||||
#else
|
||||
ErrorOr<void> connect_as_client(ByteString const& socket_path, Vector<ByteString> const& raw_urls, NewWindow new_window);
|
||||
ErrorOr<void> connect_as_server(ByteString const& socket_path);
|
||||
#endif
|
||||
|
||||
RefPtr<Core::LocalServer> m_local_server;
|
||||
OwnPtr<Core::File> m_pid_file;
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
namespace WebView {
|
||||
|
||||
MachPortServer::MachPortServer()
|
||||
MachPortServer::MachPortServer(ByteString server_port_name)
|
||||
: m_thread(Threading::Thread::construct("MachPortServer"sv, [this]() -> intptr_t { thread_loop(); return 0; }))
|
||||
, m_server_port_name(ByteString::formatted("org.ladybird.Ladybird.helper.{}", getpid()))
|
||||
, m_server_port_name(move(server_port_name))
|
||||
{
|
||||
if (auto err = allocate_server_port(); err.is_error())
|
||||
dbgln("Failed to allocate server port: {}", err.error());
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace WebView {
|
|||
class WEBVIEW_API MachPortServer {
|
||||
|
||||
public:
|
||||
MachPortServer();
|
||||
explicit MachPortServer(ByteString server_port_name);
|
||||
~MachPortServer();
|
||||
|
||||
void start();
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ struct BrowserOptions {
|
|||
DisableSQLDatabase disable_sql_database { DisableSQLDatabase::No };
|
||||
Optional<ProcessType> debug_helper_process {};
|
||||
Optional<ProcessType> profile_helper_process {};
|
||||
Optional<ByteString> webdriver_content_ipc_path {};
|
||||
Optional<ByteString> webdriver_endpoint {};
|
||||
Optional<DNSSettings> dns_settings {};
|
||||
Optional<u16> devtools_port;
|
||||
EnableContentFilter enable_content_filter { EnableContentFilter::Yes };
|
||||
|
|
|
|||
|
|
@ -45,6 +45,11 @@ void set_mach_server_name(ByteString name)
|
|||
s_mach_server_name = move(name);
|
||||
}
|
||||
|
||||
ByteString mach_server_name_for_process(StringView process_name, pid_t pid)
|
||||
{
|
||||
return ByteString::formatted("org.ladybird.{}.helper.{}", process_name, pid);
|
||||
}
|
||||
|
||||
static ErrorOr<ByteString> application_directory()
|
||||
{
|
||||
if (s_ladybird_binary_path.has_value())
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <AK/ByteString.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ WEBVIEW_API ErrorOr<Vector<ByteString>> get_paths_for_helper_process(StringView
|
|||
WEBVIEW_API extern ByteString s_ladybird_resource_root;
|
||||
WEBVIEW_API Optional<ByteString const&> mach_server_name();
|
||||
WEBVIEW_API void set_mach_server_name(ByteString name);
|
||||
WEBVIEW_API ByteString mach_server_name_for_process(StringView process_name, pid_t pid);
|
||||
|
||||
WEBVIEW_API ErrorOr<void> handle_attached_debugger();
|
||||
|
||||
|
|
|
|||
|
|
@ -650,8 +650,8 @@ void ViewImplementation::initialize_client(CreateNewClient create_new_client)
|
|||
client().async_set_system_visibility_state(m_client_state.page_index, m_system_visibility_state);
|
||||
client().async_set_document_cookie_version_buffer(m_client_state.page_index, m_document_cookie_version_buffer);
|
||||
|
||||
if (auto webdriver_content_ipc_path = Application::browser_options().webdriver_content_ipc_path; webdriver_content_ipc_path.has_value())
|
||||
client().async_connect_to_webdriver(m_client_state.page_index, *webdriver_content_ipc_path);
|
||||
if (auto webdriver_endpoint = Application::browser_options().webdriver_endpoint; webdriver_endpoint.has_value())
|
||||
client().async_connect_to_webdriver(m_client_state.page_index, *webdriver_endpoint);
|
||||
|
||||
Application::the().apply_view_options({}, *this);
|
||||
|
||||
|
|
|
|||
|
|
@ -122,11 +122,11 @@ void ConnectionFromClient::set_window_handle(u64 page_id, String handle)
|
|||
page->page().top_level_traversable()->set_window_handle(move(handle));
|
||||
}
|
||||
|
||||
void ConnectionFromClient::connect_to_webdriver(u64 page_id, ByteString webdriver_ipc_path)
|
||||
void ConnectionFromClient::connect_to_webdriver(u64 page_id, ByteString webdriver_endpoint)
|
||||
{
|
||||
if (auto page = this->page(page_id); page.has_value()) {
|
||||
// FIXME: Propagate this error back to the browser.
|
||||
if (auto result = page->connect_to_webdriver(webdriver_ipc_path); result.is_error())
|
||||
if (auto result = page->connect_to_webdriver(webdriver_endpoint); result.is_error())
|
||||
dbgln("Unable to connect to the WebDriver process: {}", result.error());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ private:
|
|||
virtual void close_server() override;
|
||||
virtual Messages::WebContentServer::GetWindowHandleResponse get_window_handle(u64 page_id) override;
|
||||
virtual void set_window_handle(u64 page_id, String handle) override;
|
||||
virtual void connect_to_webdriver(u64 page_id, ByteString webdriver_ipc_path) override;
|
||||
virtual void connect_to_webdriver(u64 page_id, ByteString webdriver_endpoint) override;
|
||||
virtual void connect_to_web_ui(u64 page_id, IPC::TransportHandle handle) override;
|
||||
virtual void connect_to_request_server(IPC::TransportHandle handle) override;
|
||||
virtual void connect_to_image_decoder(IPC::TransportHandle handle) override;
|
||||
|
|
|
|||
|
|
@ -790,10 +790,10 @@ void PageClient::page_did_take_screenshot(Gfx::ShareableBitmap const& screenshot
|
|||
client().async_did_take_screenshot(m_id, screenshot);
|
||||
}
|
||||
|
||||
ErrorOr<void> PageClient::connect_to_webdriver(ByteString const& webdriver_ipc_path)
|
||||
ErrorOr<void> PageClient::connect_to_webdriver(ByteString const& webdriver_endpoint)
|
||||
{
|
||||
VERIFY(!m_webdriver);
|
||||
m_webdriver = TRY(WebDriverConnection::connect(*this, webdriver_ipc_path));
|
||||
m_webdriver = TRY(WebDriverConnection::connect(*this, webdriver_endpoint));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public:
|
|||
virtual Web::Page& page() override { return *m_page; }
|
||||
virtual Web::Page const& page() const override { return *m_page; }
|
||||
|
||||
ErrorOr<void> connect_to_webdriver(ByteString const& webdriver_ipc_path);
|
||||
ErrorOr<void> connect_to_webdriver(ByteString const& webdriver_endpoint);
|
||||
ErrorOr<void> connect_to_web_ui(IPC::TransportHandle);
|
||||
|
||||
virtual Queue<Web::QueuedInputEvent>& input_event_queue() override;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ endpoint WebContentServer
|
|||
get_window_handle(u64 page_id) => (String handle)
|
||||
set_window_handle(u64 page_id, String handle) =|
|
||||
|
||||
connect_to_webdriver(u64 page_id, ByteString webdriver_ipc_path) =|
|
||||
connect_to_webdriver(u64 page_id, ByteString webdriver_endpoint) =|
|
||||
connect_to_web_ui(u64 page_id, IPC::TransportHandle handle) =|
|
||||
connect_to_request_server(IPC::TransportHandle handle) =|
|
||||
connect_to_image_decoder(IPC::TransportHandle handle) =|
|
||||
|
|
|
|||
|
|
@ -14,7 +14,11 @@
|
|||
#include <AK/Time.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/Socket.h>
|
||||
#if !defined(AK_OS_MACOS)
|
||||
# include <LibCore/Socket.h>
|
||||
#else
|
||||
# include <LibIPC/TransportBootstrapMach.h>
|
||||
#endif
|
||||
#include <LibHTTP/Cookie/Cookie.h>
|
||||
#include <LibHTTP/Cookie/ParsedCookie.h>
|
||||
#include <LibIPC/Transport.h>
|
||||
|
|
@ -194,16 +198,24 @@ static bool fire_an_event(FlyString const& name, Optional<Web::DOM::Element&> ta
|
|||
return target->dispatch_event(event);
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<WebDriverConnection>> WebDriverConnection::connect(Web::PageClient& page_client, ByteString const& webdriver_ipc_path)
|
||||
ErrorOr<NonnullRefPtr<WebDriverConnection>> WebDriverConnection::connect(Web::PageClient& page_client, ByteString const& webdriver_endpoint)
|
||||
{
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Trying to connect to {}", webdriver_ipc_path);
|
||||
auto socket = TRY(Core::LocalSocket::connect(webdriver_ipc_path));
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Trying to connect to {}", webdriver_endpoint);
|
||||
#if defined(AK_OS_MACOS)
|
||||
auto transport_ports = TRY(IPC::bootstrap_transport_from_mach_server(webdriver_endpoint));
|
||||
#else
|
||||
auto socket = TRY(Core::LocalSocket::connect(webdriver_endpoint));
|
||||
#endif
|
||||
|
||||
// Allow pop-ups, or otherwise /window/new won't be able to open a new tab.
|
||||
page_client.page().set_should_block_pop_ups(false);
|
||||
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Connected to WebDriver");
|
||||
#if defined(AK_OS_MACOS)
|
||||
auto transport = make<IPC::Transport>(move(transport_ports.receive_right), move(transport_ports.send_right));
|
||||
#else
|
||||
auto transport = TRY(IPC::Transport::from_socket(move(socket)));
|
||||
#endif
|
||||
auto connection = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) WebDriverConnection(move(transport), page_client)));
|
||||
connection->async_did_set_window_handle(page_client.page().top_level_traversable()->window_handle());
|
||||
return connection;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class WebDriverConnection final
|
|||
C_OBJECT_ABSTRACT(WebDriverConnection)
|
||||
|
||||
public:
|
||||
static ErrorOr<NonnullRefPtr<WebDriverConnection>> connect(Web::PageClient& page_client, ByteString const& webdriver_ipc_path);
|
||||
static ErrorOr<NonnullRefPtr<WebDriverConnection>> connect(Web::PageClient& page_client, ByteString const& webdriver_endpoint);
|
||||
virtual ~WebDriverConnection() = default;
|
||||
|
||||
void visit_edges(JS::Cell::Visitor&);
|
||||
|
|
|
|||
|
|
@ -10,9 +10,14 @@
|
|||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <LibCore/LocalServer.h>
|
||||
#include <LibCore/Socket.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#if !defined(AK_OS_MACOS)
|
||||
# include <LibCore/LocalServer.h>
|
||||
# include <LibCore/Socket.h>
|
||||
# include <LibCore/StandardPaths.h>
|
||||
#else
|
||||
# include <LibIPC/TransportBootstrapMach.h>
|
||||
# include <LibWebView/Utilities.h>
|
||||
#endif
|
||||
#include <LibCore/System.h>
|
||||
#include <LibIPC/Transport.h>
|
||||
#include <LibWeb/Crypto/Crypto.h>
|
||||
|
|
@ -123,6 +128,7 @@ Session::Session(NonnullRefPtr<Client> client, JsonObject const& capabilities, S
|
|||
, m_options(capabilities)
|
||||
, m_session_id(move(session_id))
|
||||
, m_session_flags(flags)
|
||||
, m_event_loop(Core::EventLoop::current_weak())
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -192,15 +198,67 @@ void Session::close()
|
|||
if (m_browser_process.has_value())
|
||||
MUST(Core::System::kill(m_browser_process->pid(), SIGTERM));
|
||||
|
||||
if (m_web_content_socket_path.has_value()) {
|
||||
MUST(Core::System::unlink(*m_web_content_socket_path));
|
||||
m_web_content_socket_path = {};
|
||||
}
|
||||
#if defined(AK_OS_MACOS)
|
||||
m_web_content_mach_port_server = nullptr;
|
||||
#else
|
||||
if (!m_web_content_endpoint.is_empty())
|
||||
MUST(Core::System::unlink(m_web_content_endpoint));
|
||||
#endif
|
||||
m_web_content_endpoint = {};
|
||||
|
||||
// 5. If an error has occurred in any of the steps above, return the error, otherwise return success with data null.
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<Core::LocalServer>> Session::create_server(NonnullRefPtr<ServerPromise> promise)
|
||||
ErrorOr<void> Session::accept_web_content_transport(NonnullOwnPtr<IPC::Transport> transport, NonnullRefPtr<ServerPromise> promise)
|
||||
{
|
||||
auto web_content_connection = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) WebContentConnection(move(transport))));
|
||||
dbgln("WebDriver is connected to WebContent");
|
||||
|
||||
auto connection_id = m_next_pending_connection_id++;
|
||||
// Publish the connection before the initial did_set_window_handle message can race in.
|
||||
m_pending_connections.set(connection_id, web_content_connection);
|
||||
|
||||
web_content_connection->on_close = [this, promise, connection_id]() {
|
||||
if (m_pending_connections.remove(connection_id)) {
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Pending connection {} closed before sending its handle", connection_id);
|
||||
promise->reject(Error::from_string_literal("Window was closed before sending its handle"));
|
||||
}
|
||||
};
|
||||
|
||||
web_content_connection->on_did_set_window_handle = [this, promise, connection_id](String window_handle) {
|
||||
auto maybe_pending_connection = m_pending_connections.take(connection_id);
|
||||
if (!maybe_pending_connection.has_value())
|
||||
return;
|
||||
|
||||
auto pending_connection = maybe_pending_connection.value();
|
||||
pending_connection->on_did_set_window_handle = nullptr;
|
||||
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Window {} registered with WebDriver.", window_handle);
|
||||
|
||||
pending_connection->on_close = [this, window_handle]() {
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Window {} was closed remotely.", window_handle);
|
||||
m_windows.remove(window_handle);
|
||||
if (m_windows.is_empty())
|
||||
close();
|
||||
};
|
||||
|
||||
pending_connection->async_set_page_load_strategy(m_page_load_strategy);
|
||||
pending_connection->async_set_strict_file_interactability(m_strict_file_interactiblity);
|
||||
pending_connection->async_set_user_prompt_handler(Web::WebDriver::user_prompt_handler());
|
||||
if (m_timeouts_configuration.has_value())
|
||||
pending_connection->async_set_timeouts(*m_timeouts_configuration);
|
||||
|
||||
m_windows.set(window_handle, Session::Window { window_handle, move(pending_connection) });
|
||||
|
||||
if (m_current_window_handle.is_empty())
|
||||
m_current_window_handle = window_handle;
|
||||
|
||||
promise->resolve({});
|
||||
};
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> Session::create_server(NonnullRefPtr<ServerPromise> promise)
|
||||
{
|
||||
#if defined(AK_OS_WINDOWS)
|
||||
static_assert(IsSame<IPC::Transport, IPC::TransportSocketWindows>, "Need to handle other IPC transports here");
|
||||
|
|
@ -210,12 +268,44 @@ ErrorOr<NonnullRefPtr<Core::LocalServer>> Session::create_server(NonnullRefPtr<S
|
|||
static_assert(IsSame<IPC::Transport, IPC::TransportSocket>, "Need to handle other IPC transports here");
|
||||
#endif
|
||||
|
||||
dbgln("Listening for WebDriver connection on {}", *m_web_content_socket_path);
|
||||
dbgln("Listening for WebDriver connection on {}", m_web_content_endpoint);
|
||||
|
||||
(void)Core::System::unlink(*m_web_content_socket_path);
|
||||
#if defined(AK_OS_MACOS)
|
||||
m_web_content_mach_port_server = make<WebView::MachPortServer>(m_web_content_endpoint);
|
||||
if (!m_web_content_mach_port_server->is_initialized())
|
||||
return Error::from_string_literal("Failed to initialize Mach port server for WebDriver");
|
||||
|
||||
m_web_content_mach_port_server->on_receive_child_mach_port = [this, promise](auto registration) {
|
||||
auto registration_result = m_transport_bootstrap_server.register_reply_port(registration.pid, move(registration.reply_port));
|
||||
if (registration_result.is_error()) {
|
||||
auto event_loop = m_event_loop->take();
|
||||
VERIFY(event_loop);
|
||||
event_loop->deferred_invoke([promise, error = registration_result.release_error()]() mutable {
|
||||
promise->resolve(move(error));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
registration_result.release_value().visit(
|
||||
[](IPC::TransportBootstrapMachServer::WaitingForChildTransport) {
|
||||
VERIFY_NOT_REACHED();
|
||||
},
|
||||
[this, promise](IPC::TransportBootstrapMachServer::OnDemandTransport& transport) {
|
||||
auto event_loop = m_event_loop->take();
|
||||
VERIFY(event_loop);
|
||||
event_loop->deferred_invoke([this, promise, transport = move(transport.ports)]() mutable {
|
||||
if (auto result = accept_web_content_transport(make<IPC::Transport>(move(transport.receive_right), move(transport.send_right)), promise); result.is_error())
|
||||
promise->resolve(result.release_error());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return {};
|
||||
#else
|
||||
(void)Core::System::unlink(m_web_content_endpoint);
|
||||
|
||||
auto server = Core::LocalServer::construct();
|
||||
server->listen(*m_web_content_socket_path);
|
||||
server->listen(m_web_content_endpoint);
|
||||
|
||||
server->on_accept = [this, promise](auto client_socket) {
|
||||
auto maybe_transport = IPC::Transport::from_socket(move(client_socket));
|
||||
|
|
@ -223,73 +313,31 @@ ErrorOr<NonnullRefPtr<Core::LocalServer>> Session::create_server(NonnullRefPtr<S
|
|||
promise->resolve(maybe_transport.release_error());
|
||||
return;
|
||||
}
|
||||
auto maybe_connection = adopt_nonnull_ref_or_enomem(new (nothrow) WebContentConnection(maybe_transport.release_value()));
|
||||
if (maybe_connection.is_error()) {
|
||||
promise->resolve(maybe_connection.release_error());
|
||||
return;
|
||||
}
|
||||
|
||||
dbgln("WebDriver is connected to WebContent socket");
|
||||
auto web_content_connection = maybe_connection.release_value();
|
||||
|
||||
auto connection_id = m_next_pending_connection_id++;
|
||||
|
||||
web_content_connection->on_close = [this, promise, connection_id]() {
|
||||
if (m_pending_connections.remove(connection_id)) {
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Pending connection {} closed before sending its handle", connection_id);
|
||||
promise->reject(Error::from_string_literal("Window was closed before sending its handle"));
|
||||
}
|
||||
};
|
||||
|
||||
web_content_connection->on_did_set_window_handle = [this, promise, connection_id](String window_handle) {
|
||||
auto maybe_pending_connection = m_pending_connections.take(connection_id);
|
||||
if (!maybe_pending_connection.has_value())
|
||||
return;
|
||||
|
||||
auto pending_connection = maybe_pending_connection.value();
|
||||
pending_connection->on_did_set_window_handle = nullptr;
|
||||
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Window {} registered with WebDriver.", window_handle);
|
||||
|
||||
pending_connection->on_close = [this, window_handle]() {
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Window {} was closed remotely.", window_handle);
|
||||
m_windows.remove(window_handle);
|
||||
if (m_windows.is_empty())
|
||||
close();
|
||||
};
|
||||
|
||||
pending_connection->async_set_page_load_strategy(m_page_load_strategy);
|
||||
pending_connection->async_set_strict_file_interactability(m_strict_file_interactiblity);
|
||||
pending_connection->async_set_user_prompt_handler(Web::WebDriver::user_prompt_handler());
|
||||
if (m_timeouts_configuration.has_value())
|
||||
pending_connection->async_set_timeouts(*m_timeouts_configuration);
|
||||
|
||||
m_windows.set(window_handle, Session::Window { window_handle, move(pending_connection) });
|
||||
|
||||
if (m_current_window_handle.is_empty())
|
||||
m_current_window_handle = window_handle;
|
||||
|
||||
promise->resolve({});
|
||||
};
|
||||
|
||||
m_pending_connections.set(connection_id, move(web_content_connection));
|
||||
if (auto result = accept_web_content_transport(maybe_transport.release_value(), promise); result.is_error())
|
||||
promise->resolve(result.release_error());
|
||||
};
|
||||
|
||||
server->on_accept_error = [promise](auto error) {
|
||||
promise->resolve(move(error));
|
||||
};
|
||||
|
||||
return server;
|
||||
m_web_content_server = server;
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
ErrorOr<void> Session::start(LaunchBrowserCallback const& launch_browser_callback)
|
||||
{
|
||||
auto promise = ServerPromise::construct();
|
||||
|
||||
m_web_content_socket_path = ByteString::formatted("{}/webdriver/session_{}_{}", TRY(Core::StandardPaths::runtime_directory()), Core::System::getpid(), m_session_id);
|
||||
m_web_content_server = TRY(create_server(promise));
|
||||
#if defined(AK_OS_MACOS)
|
||||
m_web_content_endpoint = ByteString::formatted("{}.{}", WebView::mach_server_name_for_process("WebDriver"sv, Core::System::getpid()), m_session_id);
|
||||
#else
|
||||
m_web_content_endpoint = ByteString::formatted("{}/webdriver/session_{}_{}", TRY(Core::StandardPaths::runtime_directory()), Core::System::getpid(), m_session_id);
|
||||
#endif
|
||||
TRY(create_server(promise));
|
||||
|
||||
m_browser_process = TRY(launch_browser_callback(*m_web_content_socket_path, m_options.headless));
|
||||
m_browser_process = TRY(launch_browser_callback(m_web_content_endpoint, m_options.headless));
|
||||
|
||||
// FIXME: Allow this to be more asynchronous. For now, this at least allows us to propagate
|
||||
// errors received while accepting the Browser and WebContent sockets.
|
||||
|
|
|
|||
|
|
@ -10,11 +10,18 @@
|
|||
|
||||
#include <AK/Error.h>
|
||||
#include <AK/JsonValue.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#if !defined(AK_OS_MACOS)
|
||||
# include <LibCore/LocalServer.h>
|
||||
#else
|
||||
# include <LibIPC/TransportBootstrapMach.h>
|
||||
# include <LibWebView/MachPortServer.h>
|
||||
#endif
|
||||
#include <LibCore/Process.h>
|
||||
#include <LibCore/Promise.h>
|
||||
#include <LibWeb/WebDriver/Capabilities.h>
|
||||
|
|
@ -85,10 +92,11 @@ public:
|
|||
private:
|
||||
Session(NonnullRefPtr<Client> client, JsonObject const& capabilities, String session_id, Web::WebDriver::SessionFlags flags);
|
||||
|
||||
ErrorOr<void> start(LaunchBrowserCallback const&);
|
||||
|
||||
using ServerPromise = Core::Promise<ErrorOr<void>>;
|
||||
ErrorOr<NonnullRefPtr<Core::LocalServer>> create_server(NonnullRefPtr<ServerPromise> promise);
|
||||
|
||||
ErrorOr<void> start(LaunchBrowserCallback const&);
|
||||
ErrorOr<void> accept_web_content_transport(NonnullOwnPtr<IPC::Transport>, NonnullRefPtr<ServerPromise> promise);
|
||||
ErrorOr<void> create_server(NonnullRefPtr<ServerPromise> promise);
|
||||
|
||||
NonnullRefPtr<Client> m_client;
|
||||
Web::WebDriver::LadybirdOptions m_options;
|
||||
|
|
@ -102,10 +110,16 @@ private:
|
|||
HashMap<u64, NonnullRefPtr<WebContentConnection>> m_pending_connections;
|
||||
u64 m_next_pending_connection_id { 0 };
|
||||
|
||||
Optional<ByteString> m_web_content_socket_path;
|
||||
ByteString m_web_content_endpoint;
|
||||
Optional<Core::Process> m_browser_process;
|
||||
NonnullRefPtr<Core::WeakEventLoopReference> m_event_loop;
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
OwnPtr<WebView::MachPortServer> m_web_content_mach_port_server;
|
||||
IPC::TransportBootstrapMachServer m_transport_bootstrap_server;
|
||||
#else
|
||||
RefPtr<Core::LocalServer> m_web_content_server;
|
||||
#endif
|
||||
|
||||
Web::WebDriver::PageLoadStrategy m_page_load_strategy { Web::WebDriver::PageLoadStrategy::Normal };
|
||||
Optional<JsonValue> m_timeouts_configuration;
|
||||
|
|
|
|||
|
|
@ -33,12 +33,15 @@ static ErrorOr<Core::Process> launch_process(StringView application, ReadonlySpa
|
|||
return result;
|
||||
}
|
||||
|
||||
static Vector<ByteString> create_arguments(ByteString const& socket_path, bool headless, bool expose_experimental_interfaces, bool force_cpu_painting, Optional<StringView> debug_process, Optional<StringView> default_time_zone)
|
||||
static Vector<ByteString> create_arguments(ByteString const& webdriver_endpoint, bool headless, bool expose_experimental_interfaces, bool force_cpu_painting, Optional<StringView> debug_process, Optional<StringView> default_time_zone)
|
||||
{
|
||||
Vector<ByteString> arguments {
|
||||
"--webdriver-content-path"sv,
|
||||
socket_path,
|
||||
};
|
||||
Vector<ByteString> arguments;
|
||||
#if defined(AK_OS_MACOS)
|
||||
arguments.append("--webdriver-mach-server-name"sv);
|
||||
#else
|
||||
arguments.append("--webdriver-content-path"sv);
|
||||
#endif
|
||||
arguments.append(webdriver_endpoint);
|
||||
|
||||
Vector<ByteString> certificate_args;
|
||||
for (auto const& certificate : certificates) {
|
||||
|
|
@ -131,8 +134,8 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
return;
|
||||
}
|
||||
|
||||
auto launch_browser_callback = [&](ByteString const& socket_path, bool headless) {
|
||||
auto arguments = create_arguments(socket_path, headless, expose_experimental_interfaces, force_cpu_painting, debug_process, default_time_zone);
|
||||
auto launch_browser_callback = [&](ByteString const& webdriver_endpoint, bool headless) {
|
||||
auto arguments = create_arguments(webdriver_endpoint, headless, expose_experimental_interfaces, force_cpu_painting, debug_process, default_time_zone);
|
||||
return launch_process("Ladybird"sv, arguments.span());
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue