1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 05:07:35 +00:00

headless-browser: Add a mode for being controlled by WebDriver

This adds command line flags for WebDriver to pass its IPC socket path
(if running on Serenity) or its FD passing socket (if running elsewhere)
for the headless-browser to connect to.
This commit is contained in:
Timothy Flynn 2022-11-21 20:03:45 -05:00 committed by Linus Groh
parent 0135a2ab5b
commit e840d27d8e
3 changed files with 74 additions and 27 deletions

View file

@ -19,6 +19,7 @@
#include <LibCore/MemoryStream.h>
#include <LibCore/Stream.h>
#include <LibCore/System.h>
#include <LibCore/SystemServerTakeover.h>
#include <LibCore/Timer.h>
#include <LibGemini/GeminiRequest.h>
#include <LibGemini/GeminiResponse.h>
@ -47,6 +48,7 @@
#include <LibWebSocket/ConnectionInfo.h>
#include <LibWebSocket/Message.h>
#include <LibWebSocket/WebSocket.h>
#include <WebContent/WebDriverConnection.h>
class HeadlessBrowserPageClient final : public Web::PageClient {
public:
@ -107,9 +109,30 @@ public:
m_screen_rect = screen_rect;
}
ErrorOr<void> connect_to_webdriver(StringView webdriver_ipc_path)
{
VERIFY(!m_webdriver);
m_webdriver = TRY(WebContent::WebDriverConnection::connect(*this, webdriver_ipc_path));
return {};
}
ErrorOr<void> connect_to_webdriver(int webdriver_fd_passing_socket)
{
VERIFY(!m_webdriver);
VERIFY(webdriver_fd_passing_socket >= 0);
auto socket = TRY(Core::take_over_socket_from_system_server("WebDriver"sv));
m_webdriver = TRY(WebContent::WebDriverConnection::try_create(move(socket), *this));
m_webdriver->set_fd_passing_socket(TRY(Core::Stream::LocalSocket::adopt_fd(webdriver_fd_passing_socket)));
return {};
}
// ^Web::PageClient
virtual bool is_connection_open() const override
{
if (m_webdriver)
return m_webdriver->is_open();
return true;
}
@ -238,6 +261,8 @@ private:
RefPtr<Gfx::PaletteImpl> m_palette_impl;
Gfx::IntRect m_screen_rect { 0, 0, 800, 600 };
Web::CSS::PreferredColorScheme m_preferred_color_scheme { Web::CSS::PreferredColorScheme::Auto };
RefPtr<WebContent::WebDriverConnection> m_webdriver;
};
class ImageCodecPluginHeadless : public Web::Platform::ImageCodecPlugin {
@ -657,6 +682,36 @@ private:
HeadlessWebSocketClientManager() { }
};
static void load_page_for_screenshot_and_exit(HeadlessBrowserPageClient& page_client, int take_screenshot_after)
{
dbgln("Taking screenshot after {} seconds", take_screenshot_after);
auto timer = Core::Timer::create_single_shot(
take_screenshot_after * 1000,
[&]() {
// FIXME: Allow passing the output path as argument
String output_file_path = "output.png";
dbgln("Saving to {}", output_file_path);
if (Core::File::exists(output_file_path))
MUST(Core::File::remove(output_file_path, Core::File::RecursionMode::Disallowed, true));
auto output_file = MUST(Core::Stream::File::open(output_file_path, Core::Stream::OpenMode::Write));
auto output_rect = page_client.screen_rect();
auto output_bitmap = MUST(Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, output_rect.size()));
page_client.paint(output_rect, output_bitmap);
auto image_buffer = Gfx::PNGWriter::encode(output_bitmap);
MUST(output_file->write(image_buffer.bytes()));
exit(0);
});
timer->start();
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
int take_screenshot_after = 1;
@ -664,6 +719,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
StringView resources_folder;
StringView error_page_url;
StringView ca_certs_path;
StringView webdriver_ipc_path;
int webdriver_fd_passing_socket { -1 };
Core::EventLoop event_loop;
Core::ArgsParser args_parser;
@ -672,9 +729,14 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
args_parser.add_option(resources_folder, "Path of the base resources folder (defaults to /res)", "resources", 'r', "resources-root-path");
args_parser.add_option(error_page_url, "URL for the error page (defaults to file:///res/html/error.html)", "error-page", 'e', "error-page-url");
args_parser.add_option(ca_certs_path, "The bundled ca certificates file", "certs", 'c', "ca-certs-path");
args_parser.add_option(webdriver_ipc_path, "Path to the WebDriver IPC socket", "webdriver-ipc-path", 0, "path");
args_parser.add_option(webdriver_fd_passing_socket, "File descriptor of the passing socket for the WebDriver connection", "webdriver-fd-passing-socket", 'd', "webdriver_fd_passing_socket");
args_parser.add_positional_argument(url, "URL to open", "url", Core::ArgsParser::Required::Yes);
args_parser.parse(arguments);
if (!webdriver_ipc_path.is_empty() && webdriver_fd_passing_socket >= 0)
return Error::from_string_view("Only one of --webdriver-ipc-path and --webdriver-fd-passing-socket may be used"sv);
Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPluginSerenity);
Web::Platform::FontPlugin::install(*new Web::Platform::FontPluginSerenity);
Web::Platform::ImageCodecPlugin::install(*new ImageCodecPluginHeadless);
@ -716,30 +778,12 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
page_client->set_viewport_rect({ 0, 0, 800, 600 });
page_client->set_screen_rect({ 0, 0, 800, 600 });
dbgln("Taking screenshot after {} seconds !", take_screenshot_after);
auto timer = Core::Timer::create_single_shot(
take_screenshot_after * 1000,
[page_client = move(page_client)] {
// FIXME: Allow passing the output path as argument
String output_file_path = "output.png";
dbgln("Saving to {}", output_file_path);
if (Core::File::exists(output_file_path))
[[maybe_unused]]
auto ignored = Core::File::remove(output_file_path, Core::File::RecursionMode::Disallowed, true);
auto output_file = MUST(Core::Stream::File::open(output_file_path, Core::Stream::OpenMode::Write));
auto output_rect = page_client->screen_rect();
auto output_bitmap = MUST(Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, output_rect.size()));
page_client->paint(output_rect, output_bitmap);
auto image_buffer = Gfx::PNGWriter::encode(output_bitmap);
MUST(output_file->write(image_buffer.bytes()));
exit(0);
});
timer->start();
if (!webdriver_ipc_path.is_empty())
TRY(page_client->connect_to_webdriver(webdriver_ipc_path));
else if (webdriver_fd_passing_socket >= 0)
TRY(page_client->connect_to_webdriver(webdriver_fd_passing_socket));
else
load_page_for_screenshot_and_exit(*page_client, take_screenshot_after);
return event_loop.exec();
}