diff --git a/Userland/Utilities/headless-browser.cpp b/Userland/Utilities/headless-browser.cpp index 79abce7eda1..165d6eac036 100644 --- a/Userland/Utilities/headless-browser.cpp +++ b/Userland/Utilities/headless-browser.cpp @@ -50,9 +50,12 @@ constexpr int DEFAULT_TIMEOUT_MS = 30000; // 30sec static StringView s_current_test_path; -struct Application : public WebView::Application { +class HeadlessWebContentView; + +class Application : public WebView::Application { WEB_VIEW_APPLICATION(Application) +public: static Application& the() { return static_cast<Application&>(WebView::Application::the()); @@ -88,6 +91,22 @@ struct Application : public WebView::Application { web_content_options.is_layout_test_mode = is_layout_test_mode ? WebView::IsLayoutTestMode::Yes : WebView::IsLayoutTestMode::No; } + ErrorOr<void> launch_services() + { + auto request_server_paths = TRY(get_paths_for_helper_process("RequestServer"sv)); + m_request_client = TRY(launch_request_server_process(request_server_paths, resources_folder)); + + auto image_decoder_paths = TRY(get_paths_for_helper_process("ImageDecoder"sv)); + m_image_decoder_client = TRY(launch_image_decoder_process(image_decoder_paths)); + + return {}; + } + + static Requests::RequestClient& request_client() { return *the().m_request_client; } + static ImageDecoderClient::Client& image_decoder_client() { return *the().m_image_decoder_client; } + + ErrorOr<HeadlessWebContentView*> create_web_view(Core::AnonymousBuffer theme, Gfx::IntSize window_size); + int screenshot_timeout { 1 }; ByteString resources_folder { s_ladybird_resource_root }; bool dump_failed_ref_tests { false }; @@ -99,6 +118,12 @@ struct Application : public WebView::Application { ByteString test_glob; bool test_dry_run { false }; bool rebaseline { false }; + +private: + RefPtr<Requests::RequestClient> m_request_client; + RefPtr<ImageDecoderClient::Client> m_image_decoder_client; + + OwnPtr<HeadlessWebContentView> m_web_view; }; Application::Application(Badge<WebView::Application>, Main::Arguments&) @@ -107,30 +132,19 @@ Application::Application(Badge<WebView::Application>, Main::Arguments&) class HeadlessWebContentView final : public WebView::ViewImplementation { public: - static ErrorOr<NonnullOwnPtr<HeadlessWebContentView>> create(Core::AnonymousBuffer theme, Gfx::IntSize const& window_size, StringView resources_folder) + static ErrorOr<NonnullOwnPtr<HeadlessWebContentView>> create(Core::AnonymousBuffer theme, Gfx::IntSize window_size) { - RefPtr<Requests::RequestClient> request_client; - RefPtr<ImageDecoderClient::Client> image_decoder_client; + auto view = TRY(adopt_nonnull_own_or_enomem(new (nothrow) HeadlessWebContentView(window_size))); - auto request_server_paths = TRY(get_paths_for_helper_process("RequestServer"sv)); - request_client = TRY(launch_request_server_process(request_server_paths, resources_folder)); - - auto image_decoder_paths = TRY(get_paths_for_helper_process("ImageDecoder"sv)); - image_decoder_client = TRY(launch_image_decoder_process(image_decoder_paths)); - - auto view = TRY(adopt_nonnull_own_or_enomem(new (nothrow) HeadlessWebContentView(image_decoder_client, request_client))); - - auto request_server_socket = TRY(connect_new_request_server_client(*request_client)); - auto image_decoder_socket = TRY(connect_new_image_decoder_client(*image_decoder_client)); + auto request_server_socket = TRY(connect_new_request_server_client(Application::request_client())); + auto image_decoder_socket = TRY(connect_new_image_decoder_client(Application::image_decoder_client())); auto candidate_web_content_paths = TRY(get_paths_for_helper_process("WebContent"sv)); view->m_client_state.client = TRY(launch_web_content_process(*view, candidate_web_content_paths, move(image_decoder_socket), move(request_server_socket))); view->client().async_update_system_theme(0, move(theme)); - - view->m_viewport_size = window_size; - view->client().async_set_viewport_size(0, view->m_viewport_size.to_type<Web::DevicePixels>()); - view->client().async_set_window_size(0, window_size.to_type<Web::DevicePixels>()); + view->client().async_set_viewport_size(0, view->viewport_size()); + view->client().async_set_window_size(0, view->viewport_size()); if (WebView::Application::chrome_options().allow_popups == WebView::AllowPopups::Yes) view->client().async_debug_request(0, "block-pop-ups"sv, "off"sv); @@ -174,12 +188,11 @@ public: } private: - HeadlessWebContentView(RefPtr<ImageDecoderClient::Client> image_decoder_client, RefPtr<Requests::RequestClient> request_client) - : m_request_client(move(request_client)) - , m_image_decoder_client(move(image_decoder_client)) + HeadlessWebContentView(Gfx::IntSize viewport_size) + : m_viewport_size(viewport_size) { - on_request_worker_agent = [this]() { - auto worker_client = MUST(launch_web_worker_process(MUST(get_paths_for_helper_process("WebWorker"sv)), *m_request_client)); + on_request_worker_agent = []() { + auto worker_client = MUST(launch_web_worker_process(MUST(get_paths_for_helper_process("WebWorker"sv)), Application::request_client())); return worker_client->dup_socket(); }; } @@ -193,11 +206,14 @@ private: Gfx::IntSize m_viewport_size; RefPtr<Core::Promise<RefPtr<Gfx::Bitmap>>> m_pending_screenshot; - - RefPtr<Requests::RequestClient> m_request_client; - RefPtr<ImageDecoderClient::Client> m_image_decoder_client; }; +ErrorOr<HeadlessWebContentView*> Application::create_web_view(Core::AnonymousBuffer theme, Gfx::IntSize window_size) +{ + m_web_view = TRY(HeadlessWebContentView::create(move(theme), window_size)); + return m_web_view.ptr(); +} + static ErrorOr<NonnullRefPtr<Core::Timer>> load_page_for_screenshot_and_exit(Core::EventLoop& event_loop, HeadlessWebContentView& view, URL::URL const& url, int screenshot_timeout) { // FIXME: Allow passing the output path as an argument. @@ -554,11 +570,8 @@ static ErrorOr<void> collect_ref_tests(Vector<Test>& tests, StringView path) return {}; } -static ErrorOr<int> run_tests(HeadlessWebContentView* view) +static ErrorOr<int> run_tests(Core::AnonymousBuffer const& theme, Gfx::IntSize window_size) { - if (view) - view->clear_content_filters(); - auto& app = Application::the(); TRY(load_test_config(app.test_root_path)); @@ -585,6 +598,9 @@ static ErrorOr<int> run_tests(HeadlessWebContentView* view) return 0; } + auto& view = *TRY(app.create_web_view(theme, window_size)); + view.clear_content_filters(); + size_t pass_count = 0; size_t fail_count = 0; size_t timeout_count = 0; @@ -614,7 +630,7 @@ static ErrorOr<int> run_tests(HeadlessWebContentView* view) continue; } - test.result = TRY(run_test(*view, test.input_path, test.expectation_path, test.mode)); + test.result = TRY(run_test(view, test.input_path, test.expectation_path, test.mode)); switch (*test.result) { case TestResult::Pass: ++pass_count; @@ -644,7 +660,7 @@ static ErrorOr<int> run_tests(HeadlessWebContentView* view) } if (app.dump_gc_graph) { - auto path = view->dump_gc_graph(); + auto path = view.dump_gc_graph(); if (path.is_error()) { warnln("Failed to dump GC graph: {}", path.error()); } else { @@ -662,6 +678,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) platform_init(); auto app = Application::create(arguments, "about:newtab"sv); + TRY(app->launch_services()); Core::ResourceImplementation::install(make<Core::ResourceImplementationFile>(MUST(String::from_byte_string(app->resources_folder)))); @@ -672,17 +689,11 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) static constexpr Gfx::IntSize window_size { 800, 600 }; if (!app->test_root_path.is_empty()) { - OwnPtr<HeadlessWebContentView> view; - if (!app->test_dry_run) - view = TRY(HeadlessWebContentView::create(move(theme), window_size, app->resources_folder)); - - auto absolute_test_root_path = LexicalPath::absolute_path(TRY(FileSystem::current_working_directory()), app->test_root_path); - app->test_root_path = absolute_test_root_path; - - return run_tests(view); + app->test_root_path = LexicalPath::absolute_path(TRY(FileSystem::current_working_directory()), app->test_root_path); + return run_tests(theme, window_size); } - auto view = TRY(HeadlessWebContentView::create(move(theme), window_size, app->resources_folder)); + auto& view = *TRY(app->create_web_view(move(theme), window_size)); VERIFY(!WebView::Application::chrome_options().urls.is_empty()); auto const& url = WebView::Application::chrome_options().urls.first(); @@ -692,17 +703,17 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) } if (app->dump_layout_tree) { - TRY(run_dump_test(*view, url, ""sv, TestMode::Layout)); + TRY(run_dump_test(view, url, ""sv, TestMode::Layout)); return 0; } if (app->dump_text) { - TRY(run_dump_test(*view, url, ""sv, TestMode::Text)); + TRY(run_dump_test(view, url, ""sv, TestMode::Text)); return 0; } if (!WebView::Application::chrome_options().webdriver_content_ipc_path.has_value()) { - auto timer = TRY(load_page_for_screenshot_and_exit(Core::EventLoop::current(), *view, url, app->screenshot_timeout)); + auto timer = TRY(load_page_for_screenshot_and_exit(Core::EventLoop::current(), view, url, app->screenshot_timeout)); return app->execute(); }