diff --git a/Libraries/LibIPC/TransportBootstrapMach.cpp b/Libraries/LibIPC/TransportBootstrapMach.cpp index 4d11841471a..63e7c9d5f44 100644 --- a/Libraries/LibIPC/TransportBootstrapMach.cpp +++ b/Libraries/LibIPC/TransportBootstrapMach.cpp @@ -5,39 +5,18 @@ */ #include -#include #include +#include #include #include #include #include #include +#include namespace IPC { -static ErrorOr 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 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 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 bootstrap_transport_from_mach_server(String return bootstrap_transport_from_server_port(server_port); } -ErrorOr 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(name_bytes.size()); - TRY(write_exact(socket_fd, ReadonlyBytes { reinterpret_cast(&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(&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 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 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::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 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 { send_transport_ports_to_child(move(reply_port), move(waiting.ports)); + return WaitingForChildTransport {}; }, - [&](WaitingForReplyPort&) { + [&](WaitingForReplyPort&) -> ErrorOr { VERIFY_NOT_REACHED(); }); } diff --git a/Libraries/LibIPC/TransportBootstrapMach.h b/Libraries/LibIPC/TransportBootstrapMach.h index e3687fc628d..30a12de074a 100644 --- a/Libraries/LibIPC/TransportBootstrapMach.h +++ b/Libraries/LibIPC/TransportBootstrapMach.h @@ -16,7 +16,6 @@ #endif #include -#include #include namespace IPC { @@ -28,7 +27,6 @@ struct TransportBootstrapMachPorts { ErrorOr bootstrap_transport_from_mach_server(StringView server_name); ErrorOr bootstrap_transport_from_server_port(Core::MachPort const& server_port); -ErrorOr 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; + void register_pending_transport(pid_t, TransportBootstrapMachPorts); - void register_reply_port(pid_t, Core::MachPort reply_port); + ErrorOr register_reply_port(pid_t, Core::MachPort reply_port); private: struct WaitingForPorts { @@ -49,6 +54,7 @@ private: using PendingBootstrapState = Variant; static void send_transport_ports_to_child(Core::MachPort reply_port, TransportBootstrapMachPorts ports); + static ErrorOr create_on_demand_local_transport(Core::MachPort reply_port); Threading::Mutex m_pending_bootstrap_mutex; HashMap m_pending_bootstrap; diff --git a/Libraries/LibIPC/TransportMachPort.cpp b/Libraries/LibIPC/TransportMachPort.cpp index 0c030df49fc..9a65fb72cfe 100644 --- a/Libraries/LibIPC/TransportMachPort.cpp +++ b/Libraries/LibIPC/TransportMachPort.cpp @@ -7,9 +7,7 @@ #include #include #include -#include #include -#include #include #include @@ -48,12 +46,6 @@ static Attachment attachment_from_descriptor(mach_msg_port_descriptor_t const& d } } -ErrorOr> TransportMachPort::from_socket(NonnullOwnPtr socket) -{ - auto ports = TRY(bootstrap_transport_over_socket(*socket)); - return make(move(ports.receive_right), move(ports.send_right)); -} - ErrorOr TransportMachPort::create_paired() { auto port_a_recv = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive)); diff --git a/Libraries/LibIPC/TransportMachPort.h b/Libraries/LibIPC/TransportMachPort.h index a624e22776a..7ce30505c44 100644 --- a/Libraries/LibIPC/TransportMachPort.h +++ b/Libraries/LibIPC/TransportMachPort.h @@ -37,11 +37,6 @@ public: }; static ErrorOr 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> from_socket(NonnullOwnPtr socket); - TransportMachPort(Core::MachPort receive_right, Core::MachPort send_right); ~TransportMachPort(); diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index 16859827fb8..dbff5025a6f 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -27,6 +27,7 @@ #include #if defined(AK_OS_MACOS) +# include # include # include #endif @@ -88,12 +89,31 @@ ErrorOr Application::initialize(Main::Arguments const& arguments) #endif #if defined(AK_OS_MACOS) - m_mach_port_server = make(); + m_mach_port_server = make(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(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 Application::initialize(Main::Arguments const& arguments) Optional devtools_port; Optional debug_process; Optional profile_process; - Optional webdriver_content_ipc_path; + Optional webdriver_endpoint; Optional user_agent_preset; Optional dns_server_address; Optional default_time_zone; @@ -173,7 +193,11 @@ ErrorOr 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 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> 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 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)> handler) +{ + m_on_browser_process_transport = move(handler); +} +#endif + Optional Application::find_process(pid_t pid) { return m_process_manager->find_process(pid); diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index a5355ba16de..4537b0cfdbd 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -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)> handler); #endif ErrorOr> launch_web_content_process(ViewImplementation&); @@ -270,6 +273,7 @@ private: #if defined(AK_OS_MACOS) OwnPtr m_mach_port_server; IPC::TransportBootstrapMachServer m_transport_bootstrap_server; + Function)> m_on_browser_process_transport; #endif OwnPtr m_devtools; diff --git a/Libraries/LibWebView/BrowserProcess.cpp b/Libraries/LibWebView/BrowserProcess.cpp index 152d8b3edb1..526577ba776 100644 --- a/Libraries/LibWebView/BrowserProcess.cpp +++ b/Libraries/LibWebView/BrowserProcess.cpp @@ -10,9 +10,13 @@ #include #include #include +#if defined(AK_OS_MACOS) +# include +#endif #include #include #include +#include namespace WebView { @@ -33,11 +37,19 @@ ErrorOr BrowserProcess::connect(Vector BrowserProcess::connect(Vector BrowserProcess::connect_as_client(pid_t pid, Vector 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(move(transport_ports.receive_right), move(transport_ports.send_right))); + + switch (new_window) { + case NewWindow::Yes: + if (!client->send_sync_but_allow_failure(raw_urls)) + dbgln("Failed to send CreateNewWindow message to UIProcess"); + return {}; + case NewWindow::No: + if (!client->send_sync_but_allow_failure(raw_urls)) + dbgln("Failed to send CreateNewTab message to UIProcess"); + return {}; + } + + VERIFY_NOT_REACHED(); +} +#else ErrorOr BrowserProcess::connect_as_client(ByteString const& socket_path, Vector const& raw_urls, NewWindow new_window) { auto socket = TRY(Core::LocalSocket::connect(socket_path)); @@ -65,7 +97,17 @@ ErrorOr BrowserProcess::connect_as_client(ByteString const& socket_path, V VERIFY_NOT_REACHED(); } +#endif +#if defined(AK_OS_MACOS) +ErrorOr BrowserProcess::connect_as_server() +{ + Application::the().set_browser_process_transport_handler([this](auto transport) { + accept_transport(move(transport)); + }); + return {}; +} +#else ErrorOr BrowserProcess::connect_as_server(ByteString const& socket_path) { auto socket_fd = TRY(Process::create_ipc_socket(socket_path)); @@ -85,6 +127,7 @@ ErrorOr BrowserProcess::connect_as_server(ByteString const& socket_path) return {}; } +#endif void BrowserProcess::accept_transport(NonnullOwnPtr transport) { @@ -102,6 +145,10 @@ void BrowserProcess::accept_transport(NonnullOwnPtr 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) diff --git a/Libraries/LibWebView/BrowserProcess.h b/Libraries/LibWebView/BrowserProcess.h index ec31668cf0c..d93445dc629 100644 --- a/Libraries/LibWebView/BrowserProcess.h +++ b/Libraries/LibWebView/BrowserProcess.h @@ -60,8 +60,13 @@ public: private: void accept_transport(NonnullOwnPtr); +#if defined(AK_OS_MACOS) + ErrorOr connect_as_client(pid_t pid, Vector const& raw_urls, NewWindow new_window); + ErrorOr connect_as_server(); +#else ErrorOr connect_as_client(ByteString const& socket_path, Vector const& raw_urls, NewWindow new_window); ErrorOr connect_as_server(ByteString const& socket_path); +#endif RefPtr m_local_server; OwnPtr m_pid_file; diff --git a/Libraries/LibWebView/MachPortServer.cpp b/Libraries/LibWebView/MachPortServer.cpp index e5495ba3795..66b786c9404 100644 --- a/Libraries/LibWebView/MachPortServer.cpp +++ b/Libraries/LibWebView/MachPortServer.cpp @@ -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()); diff --git a/Libraries/LibWebView/MachPortServer.h b/Libraries/LibWebView/MachPortServer.h index 6dbc753e337..a105413f4dd 100644 --- a/Libraries/LibWebView/MachPortServer.h +++ b/Libraries/LibWebView/MachPortServer.h @@ -22,7 +22,7 @@ namespace WebView { class WEBVIEW_API MachPortServer { public: - MachPortServer(); + explicit MachPortServer(ByteString server_port_name); ~MachPortServer(); void start(); diff --git a/Libraries/LibWebView/Options.h b/Libraries/LibWebView/Options.h index a8a5f8374cb..2973cffb3de 100644 --- a/Libraries/LibWebView/Options.h +++ b/Libraries/LibWebView/Options.h @@ -87,7 +87,7 @@ struct BrowserOptions { DisableSQLDatabase disable_sql_database { DisableSQLDatabase::No }; Optional debug_helper_process {}; Optional profile_helper_process {}; - Optional webdriver_content_ipc_path {}; + Optional webdriver_endpoint {}; Optional dns_settings {}; Optional devtools_port; EnableContentFilter enable_content_filter { EnableContentFilter::Yes }; diff --git a/Libraries/LibWebView/Utilities.cpp b/Libraries/LibWebView/Utilities.cpp index ce20bcbbd8e..93ec40f1703 100644 --- a/Libraries/LibWebView/Utilities.cpp +++ b/Libraries/LibWebView/Utilities.cpp @@ -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 application_directory() { if (s_ladybird_binary_path.has_value()) diff --git a/Libraries/LibWebView/Utilities.h b/Libraries/LibWebView/Utilities.h index 0efd72c7172..35cca317eab 100644 --- a/Libraries/LibWebView/Utilities.h +++ b/Libraries/LibWebView/Utilities.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,7 @@ WEBVIEW_API ErrorOr> get_paths_for_helper_process(StringView WEBVIEW_API extern ByteString s_ladybird_resource_root; WEBVIEW_API Optional 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 handle_attached_debugger(); diff --git a/Libraries/LibWebView/ViewImplementation.cpp b/Libraries/LibWebView/ViewImplementation.cpp index e6a9bd80c19..87289bdb8e8 100644 --- a/Libraries/LibWebView/ViewImplementation.cpp +++ b/Libraries/LibWebView/ViewImplementation.cpp @@ -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); diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index 8e995130122..a89624f2217 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -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()); } } diff --git a/Services/WebContent/ConnectionFromClient.h b/Services/WebContent/ConnectionFromClient.h index 59fc2df9789..5a8e6022f40 100644 --- a/Services/WebContent/ConnectionFromClient.h +++ b/Services/WebContent/ConnectionFromClient.h @@ -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; diff --git a/Services/WebContent/PageClient.cpp b/Services/WebContent/PageClient.cpp index 432fda56877..a99ec3a6e83 100644 --- a/Services/WebContent/PageClient.cpp +++ b/Services/WebContent/PageClient.cpp @@ -790,10 +790,10 @@ void PageClient::page_did_take_screenshot(Gfx::ShareableBitmap const& screenshot client().async_did_take_screenshot(m_id, screenshot); } -ErrorOr PageClient::connect_to_webdriver(ByteString const& webdriver_ipc_path) +ErrorOr 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 {}; } diff --git a/Services/WebContent/PageClient.h b/Services/WebContent/PageClient.h index 8b004c42b29..c2cd2b6cb96 100644 --- a/Services/WebContent/PageClient.h +++ b/Services/WebContent/PageClient.h @@ -45,7 +45,7 @@ public: virtual Web::Page& page() override { return *m_page; } virtual Web::Page const& page() const override { return *m_page; } - ErrorOr connect_to_webdriver(ByteString const& webdriver_ipc_path); + ErrorOr connect_to_webdriver(ByteString const& webdriver_endpoint); ErrorOr connect_to_web_ui(IPC::TransportHandle); virtual Queue& input_event_queue() override; diff --git a/Services/WebContent/WebContentServer.ipc b/Services/WebContent/WebContentServer.ipc index cc2d93d7afd..2342e4e0c2f 100644 --- a/Services/WebContent/WebContentServer.ipc +++ b/Services/WebContent/WebContentServer.ipc @@ -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) =| diff --git a/Services/WebContent/WebDriverConnection.cpp b/Services/WebContent/WebDriverConnection.cpp index 391e69466b3..7eee7906872 100644 --- a/Services/WebContent/WebDriverConnection.cpp +++ b/Services/WebContent/WebDriverConnection.cpp @@ -14,7 +14,11 @@ #include #include #include -#include +#if !defined(AK_OS_MACOS) +# include +#else +# include +#endif #include #include #include @@ -194,16 +198,24 @@ static bool fire_an_event(FlyString const& name, Optional ta return target->dispatch_event(event); } -ErrorOr> WebDriverConnection::connect(Web::PageClient& page_client, ByteString const& webdriver_ipc_path) +ErrorOr> 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(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; diff --git a/Services/WebContent/WebDriverConnection.h b/Services/WebContent/WebDriverConnection.h index f089ed2cf63..11854ac0c38 100644 --- a/Services/WebContent/WebDriverConnection.h +++ b/Services/WebContent/WebDriverConnection.h @@ -34,7 +34,7 @@ class WebDriverConnection final C_OBJECT_ABSTRACT(WebDriverConnection) public: - static ErrorOr> connect(Web::PageClient& page_client, ByteString const& webdriver_ipc_path); + static ErrorOr> connect(Web::PageClient& page_client, ByteString const& webdriver_endpoint); virtual ~WebDriverConnection() = default; void visit_edges(JS::Cell::Visitor&); diff --git a/Services/WebDriver/Session.cpp b/Services/WebDriver/Session.cpp index 07a00983a99..30700e964dc 100644 --- a/Services/WebDriver/Session.cpp +++ b/Services/WebDriver/Session.cpp @@ -10,9 +10,14 @@ #include #include -#include -#include -#include +#if !defined(AK_OS_MACOS) +# include +# include +# include +#else +# include +# include +#endif #include #include #include @@ -123,6 +128,7 @@ Session::Session(NonnullRefPtr 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> Session::create_server(NonnullRefPtr promise) +ErrorOr Session::accept_web_content_transport(NonnullOwnPtr transport, NonnullRefPtr 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 Session::create_server(NonnullRefPtr promise) { #if defined(AK_OS_WINDOWS) static_assert(IsSame, "Need to handle other IPC transports here"); @@ -210,12 +268,44 @@ ErrorOr> Session::create_server(NonnullRefPtr, "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(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(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> Session::create_server(NonnullRefPtrresolve(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 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. diff --git a/Services/WebDriver/Session.h b/Services/WebDriver/Session.h index 286fde3bf43..baab0a3572d 100644 --- a/Services/WebDriver/Session.h +++ b/Services/WebDriver/Session.h @@ -10,11 +10,18 @@ #include #include +#include #include #include #include #include #include +#if !defined(AK_OS_MACOS) +# include +#else +# include +# include +#endif #include #include #include @@ -85,10 +92,11 @@ public: private: Session(NonnullRefPtr client, JsonObject const& capabilities, String session_id, Web::WebDriver::SessionFlags flags); - ErrorOr start(LaunchBrowserCallback const&); - using ServerPromise = Core::Promise>; - ErrorOr> create_server(NonnullRefPtr promise); + + ErrorOr start(LaunchBrowserCallback const&); + ErrorOr accept_web_content_transport(NonnullOwnPtr, NonnullRefPtr promise); + ErrorOr create_server(NonnullRefPtr promise); NonnullRefPtr m_client; Web::WebDriver::LadybirdOptions m_options; @@ -102,10 +110,16 @@ private: HashMap> m_pending_connections; u64 m_next_pending_connection_id { 0 }; - Optional m_web_content_socket_path; + ByteString m_web_content_endpoint; Optional m_browser_process; + NonnullRefPtr m_event_loop; +#if defined(AK_OS_MACOS) + OwnPtr m_web_content_mach_port_server; + IPC::TransportBootstrapMachServer m_transport_bootstrap_server; +#else RefPtr m_web_content_server; +#endif Web::WebDriver::PageLoadStrategy m_page_load_strategy { Web::WebDriver::PageLoadStrategy::Normal }; Optional m_timeouts_configuration; diff --git a/Services/WebDriver/main.cpp b/Services/WebDriver/main.cpp index 2b22c000bdf..0981d5924ed 100644 --- a/Services/WebDriver/main.cpp +++ b/Services/WebDriver/main.cpp @@ -33,12 +33,15 @@ static ErrorOr launch_process(StringView application, ReadonlySpa return result; } -static Vector create_arguments(ByteString const& socket_path, bool headless, bool expose_experimental_interfaces, bool force_cpu_painting, Optional debug_process, Optional default_time_zone) +static Vector create_arguments(ByteString const& webdriver_endpoint, bool headless, bool expose_experimental_interfaces, bool force_cpu_painting, Optional debug_process, Optional default_time_zone) { - Vector arguments { - "--webdriver-content-path"sv, - socket_path, - }; + Vector 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 certificate_args; for (auto const& certificate : certificates) { @@ -131,8 +134,8 @@ ErrorOr 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()); };