diff --git a/Userland/Applications/Browser/Browser.h b/Userland/Applications/Browser/Browser.h index f637073014..ad901076c3 100644 --- a/Userland/Applications/Browser/Browser.h +++ b/Userland/Applications/Browser/Browser.h @@ -21,5 +21,6 @@ extern HashMap g_proxy_mappings; extern bool g_content_filters_enabled; extern IconBag g_icon_bag; extern RefPtr g_web_driver_connection; +extern String g_webdriver_content_ipc_path; } diff --git a/Userland/Applications/Browser/Tab.cpp b/Userland/Applications/Browser/Tab.cpp index 8141f1c0d8..4559f7a3ad 100644 --- a/Userland/Applications/Browser/Tab.cpp +++ b/Userland/Applications/Browser/Tab.cpp @@ -126,8 +126,7 @@ Tab::Tab(BrowserWindow& window) m_web_content_view->set_content_filters({}); m_web_content_view->set_proxy_mappings(g_proxies, g_proxy_mappings); - - if (!g_web_driver_connection.is_null()) + if (!g_webdriver_content_ipc_path.is_empty()) enable_webdriver_mode(); auto& go_back_button = toolbar.add_action(window.go_back_action()); @@ -668,7 +667,7 @@ void Tab::hide_event(GUI::HideEvent&) void Tab::enable_webdriver_mode() { - m_web_content_view->set_is_webdriver_active(true); + m_web_content_view->connect_to_webdriver(Browser::g_webdriver_content_ipc_path); auto& webdriver_banner = *find_descendant_of_type_named("webdriver_banner"); webdriver_banner.set_visible(true); } diff --git a/Userland/Applications/Browser/main.cpp b/Userland/Applications/Browser/main.cpp index 85126f7a3d..aee870bdf6 100644 --- a/Userland/Applications/Browser/main.cpp +++ b/Userland/Applications/Browser/main.cpp @@ -41,6 +41,7 @@ Vector g_proxies; HashMap g_proxy_mappings; IconBag g_icon_bag; RefPtr g_web_driver_connection; +String g_webdriver_content_ipc_path; } @@ -68,11 +69,12 @@ ErrorOr serenity_main(Main::Arguments arguments) TRY(Core::System::pledge("stdio recvfd sendfd unix fattr cpath rpath wpath proc exec")); Vector specified_urls; - String webdriver_ipc_path; + String webdriver_browser_ipc_path; Core::ArgsParser args_parser; args_parser.add_positional_argument(specified_urls, "URLs to open", "url", Core::ArgsParser::Required::No); - args_parser.add_option(webdriver_ipc_path, "Path to WebDriver IPC", "webdriver", 0, "path"); + args_parser.add_option(webdriver_browser_ipc_path, "Path to WebDriver IPC file for Browser", "webdriver-browser-path", 0, "path"); + args_parser.add_option(Browser::g_webdriver_content_ipc_path, "Path to WebDriver IPC for WebContent", "webdriver-content-path", 0, "path"); args_parser.parse(arguments); @@ -87,9 +89,9 @@ ErrorOr serenity_main(Main::Arguments arguments) TRY(Desktop::Launcher::add_allowed_url(URL::create_with_file_scheme(Core::StandardPaths::downloads_directory()))); TRY(Desktop::Launcher::seal_allowlist()); - if (!webdriver_ipc_path.is_empty()) { + if (!webdriver_browser_ipc_path.is_empty()) { specified_urls.empend("about:blank"); - TRY(Core::System::unveil(webdriver_ipc_path.view(), "rw"sv)); + TRY(Core::System::unveil(webdriver_browser_ipc_path, "rw"sv)); } TRY(Core::System::unveil("/sys/kernel/processes", "r")); @@ -147,13 +149,8 @@ ErrorOr serenity_main(Main::Arguments arguments) Browser::CookieJar cookie_jar; auto window = Browser::BrowserWindow::construct(cookie_jar, first_url); - if (!webdriver_ipc_path.is_empty()) { - Browser::g_web_driver_connection = TRY(Browser::WebDriverConnection::connect_to_webdriver(window, webdriver_ipc_path)); - - // The first tab is created with the BrowserWindow above, so we have to do this - // manually once after establishing the connection. - window->active_tab().enable_webdriver_mode(); - } + if (!webdriver_browser_ipc_path.is_empty()) + Browser::g_web_driver_connection = TRY(Browser::WebDriverConnection::connect_to_webdriver(window, webdriver_browser_ipc_path)); auto content_filters_watcher = TRY(Core::FileWatcher::create()); content_filters_watcher->on_change = [&](Core::FileWatcherEvent const&) { diff --git a/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp b/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp index d04df78f99..a354601df4 100644 --- a/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp +++ b/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp @@ -595,9 +595,9 @@ void OutOfProcessWebView::set_preferred_color_scheme(Web::CSS::PreferredColorSch client().async_set_preferred_color_scheme(color_scheme); } -void OutOfProcessWebView::set_is_webdriver_active(bool is_webdriver_enabled) +void OutOfProcessWebView::connect_to_webdriver(String const& webdriver_ipc_path) { - client().async_set_is_webdriver_active(is_webdriver_enabled); + client().async_connect_to_webdriver(webdriver_ipc_path); } void OutOfProcessWebView::set_window_position(Gfx::IntPoint const& position) diff --git a/Userland/Libraries/LibWebView/OutOfProcessWebView.h b/Userland/Libraries/LibWebView/OutOfProcessWebView.h index abf43cc71e..a2d24d455b 100644 --- a/Userland/Libraries/LibWebView/OutOfProcessWebView.h +++ b/Userland/Libraries/LibWebView/OutOfProcessWebView.h @@ -79,7 +79,7 @@ public: void set_content_filters(Vector); void set_proxy_mappings(Vector proxies, HashMap mappings); void set_preferred_color_scheme(Web::CSS::PreferredColorScheme); - void set_is_webdriver_active(bool); + void connect_to_webdriver(String const& webdriver_ipc_path); void set_window_position(Gfx::IntPoint const&); void set_window_size(Gfx::IntSize const&); diff --git a/Userland/Services/WebDriver/CMakeLists.txt b/Userland/Services/WebDriver/CMakeLists.txt index 61bda04597..2a2787f145 100644 --- a/Userland/Services/WebDriver/CMakeLists.txt +++ b/Userland/Services/WebDriver/CMakeLists.txt @@ -1,6 +1,7 @@ serenity_component( WebDriver TARGETS WebDriver + DEPENDS WebContent ) set(SOURCES @@ -8,12 +9,15 @@ set(SOURCES Client.cpp Session.cpp TimeoutsConfiguration.cpp + WebContentConnection.cpp main.cpp ) set(GENERATED_SOURCES ../../Applications/Browser/WebDriverSessionClientEndpoint.h ../../Applications/Browser/WebDriverSessionServerEndpoint.h + ../../Services/WebContent/WebDriverClientEndpoint.h + ../../Services/WebContent/WebDriverServerEndpoint.h ) serenity_bin(WebDriver) diff --git a/Userland/Services/WebDriver/Session.cpp b/Userland/Services/WebDriver/Session.cpp index a36801ed7a..6046a69394 100644 --- a/Userland/Services/WebDriver/Session.cpp +++ b/Userland/Services/WebDriver/Session.cpp @@ -59,29 +59,75 @@ ErrorOr Session::check_for_open_top_level_browsing_ return {}; } -ErrorOr Session::start() +ErrorOr> Session::create_server(String const& socket_path, ServerType type, NonnullRefPtr promise) { - auto socket_path = String::formatted("/tmp/browser_webdriver_{}_{}", getpid(), m_id); dbgln("Listening for WebDriver connection on {}", socket_path); - // FIXME: Use Core::LocalServer - struct sockaddr_un addr; - int listen_socket = TRY(Core::System::socket(AF_UNIX, SOCK_STREAM, 0)); - ::memset(&addr, 0, sizeof(struct sockaddr_un)); - addr.sun_family = AF_UNIX; - ::strncpy(addr.sun_path, socket_path.characters(), sizeof(addr.sun_path) - 1); + auto server = TRY(Core::LocalServer::try_create()); + server->listen(socket_path); - TRY(Core::System::bind(listen_socket, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un))); - TRY(Core::System::listen(listen_socket, 1)); + server->on_accept = [this, type, promise](auto client_socket) mutable { + switch (type) { + case ServerType::Browser: { + auto maybe_connection = adopt_nonnull_ref_or_enomem(new (nothrow) BrowserConnection(move(client_socket), m_client, session_id())); + if (maybe_connection.is_error()) { + promise->resolve(maybe_connection.release_error()); + return; + } + + dbgln("WebDriver is connected to Browser socket"); + m_browser_connection = maybe_connection.release_value(); + break; + } + + case ServerType::WebContent: { + auto maybe_connection = adopt_nonnull_ref_or_enomem(new (nothrow) WebContentConnection(move(client_socket), m_client, session_id())); + if (maybe_connection.is_error()) { + promise->resolve(maybe_connection.release_error()); + return; + } + + dbgln("WebDriver is connected to WebContent socket"); + m_web_content_connection = maybe_connection.release_value(); + break; + } + } + + if (m_browser_connection && m_web_content_connection) + promise->resolve({}); + }; + + server->on_accept_error = [promise](auto error) mutable { + promise->resolve(move(error)); + }; + + return server; +} + +ErrorOr Session::start() +{ + auto promise = TRY(ServerPromise::try_create()); + + auto browser_socket_path = String::formatted("/tmp/webdriver/browser_{}_{}", getpid(), m_id); + auto browser_server = TRY(create_server(browser_socket_path, ServerType::Browser, promise)); + + auto web_content_socket_path = String::formatted("/tmp/webdriver/content_{}_{}", getpid(), m_id); + auto web_content_server = TRY(create_server(web_content_socket_path, ServerType::WebContent, promise)); + + char const* argv[] = { + "/bin/Browser", + "--webdriver-browser-path", + browser_socket_path.characters(), + "--webdriver-content-path", + web_content_socket_path.characters(), + nullptr, + }; - char const* argv[] = { "/bin/Browser", "--webdriver", socket_path.characters(), nullptr }; TRY(Core::System::posix_spawn("/bin/Browser"sv, nullptr, nullptr, const_cast(argv), environ)); - int data_socket = TRY(Core::System::accept(listen_socket, nullptr, nullptr)); - auto socket = TRY(Core::Stream::LocalSocket::adopt_fd(data_socket)); - TRY(socket->set_blocking(true)); - m_browser_connection = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) BrowserConnection(move(socket), m_client, session_id()))); - dbgln("Browser is connected"); + // FIXME: Allow this to be more asynchronous. For now, this at least allows us to propogate + // errors received while accepting the Browser and WebContent sockets. + TRY(promise->await()); m_started = true; m_windows.set("main", make("main", true)); diff --git a/Userland/Services/WebDriver/Session.h b/Userland/Services/WebDriver/Session.h index 7a1d33bf91..8e7885fdfd 100644 --- a/Userland/Services/WebDriver/Session.h +++ b/Userland/Services/WebDriver/Session.h @@ -11,10 +11,12 @@ #include #include #include +#include #include #include #include #include +#include #include namespace WebDriver { @@ -97,13 +99,20 @@ private: ErrorOr, Web::WebDriver::Error> locator_strategy_tag_name(LocalElement const&, StringView); ErrorOr, Web::WebDriver::Error> locator_strategy_x_path(LocalElement const&, StringView); + enum class ServerType { + Browser, + WebContent, + }; + using ServerPromise = Core::Promise>; + ErrorOr> create_server(String const& socket_path, ServerType type, NonnullRefPtr promise); + NonnullRefPtr m_client; bool m_started { false }; unsigned m_id { 0 }; HashMap> m_windows; String m_current_window_handle; - RefPtr m_local_server; RefPtr m_browser_connection; + RefPtr m_web_content_connection; // https://w3c.github.io/webdriver/#dfn-session-script-timeout TimeoutsConfiguration m_timeouts_configuration; diff --git a/Userland/Services/WebDriver/WebContentConnection.cpp b/Userland/Services/WebDriver/WebContentConnection.cpp new file mode 100644 index 0000000000..4c5560819e --- /dev/null +++ b/Userland/Services/WebDriver/WebContentConnection.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace WebDriver { + +WebContentConnection::WebContentConnection(NonnullOwnPtr socket, NonnullRefPtr client, unsigned session_id) + : IPC::ConnectionFromClient(*this, move(socket), 1) + , m_client(move(client)) + , m_session_id(session_id) +{ +} + +void WebContentConnection::die() +{ + dbgln_if(WEBDRIVER_DEBUG, "Session {} was closed remotely. Shutting down...", m_session_id); + m_client->close_session(m_session_id); +} + +} diff --git a/Userland/Services/WebDriver/WebContentConnection.h b/Userland/Services/WebDriver/WebContentConnection.h new file mode 100644 index 0000000000..b65d4b3f47 --- /dev/null +++ b/Userland/Services/WebDriver/WebContentConnection.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace WebDriver { + +class Client; + +class WebContentConnection + : public IPC::ConnectionFromClient { + C_OBJECT_ABSTRACT(WebContentConnection) +public: + WebContentConnection(NonnullOwnPtr socket, NonnullRefPtr client, unsigned session_id); + + virtual void die() override; + +private: + NonnullRefPtr m_client; + unsigned m_session_id { 0 }; +}; + +} diff --git a/Userland/Services/WebDriver/main.cpp b/Userland/Services/WebDriver/main.cpp index 01965aaeb7..50adadc772 100644 --- a/Userland/Services/WebDriver/main.cpp +++ b/Userland/Services/WebDriver/main.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -35,6 +36,9 @@ ErrorOr serenity_main(Main::Arguments arguments) return 1; } + TRY(Core::System::pledge("stdio accept cpath rpath recvfd inet unix proc exec fattr")); + + TRY(Core::Directory::create("/tmp/webdriver"sv, Core::Directory::CreateDirectories::Yes)); TRY(Core::System::pledge("stdio accept rpath recvfd inet unix proc exec fattr")); Core::EventLoop loop; @@ -67,7 +71,7 @@ ErrorOr serenity_main(Main::Arguments arguments) TRY(Core::System::unveil("/bin/Browser", "rx")); TRY(Core::System::unveil("/etc/timezone", "r")); TRY(Core::System::unveil("/res/icons", "r")); - TRY(Core::System::unveil("/tmp", "rwc")); + TRY(Core::System::unveil("/tmp/webdriver", "rwc")); TRY(Core::System::unveil(nullptr, nullptr)); TRY(Core::System::pledge("stdio accept rpath recvfd unix proc exec fattr"));