| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2022, Dex♪ <dexes.ttp@gmail.com> | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |  * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org> | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  |  * Copyright (c) 2023, Andreas Kling <kling@serenityos.org> | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | #include <AK/Badge.h>
 | 
					
						
							|  |  |  | #include <AK/DeprecatedString.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-13 07:23:32 -04:00
										 |  |  | #include <AK/Function.h>
 | 
					
						
							| 
									
										
										
										
											2022-07-18 20:23:19 +02:00
										 |  |  | #include <AK/LexicalPath.h>
 | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | #include <AK/NonnullOwnPtr.h>
 | 
					
						
							| 
									
										
										
										
											2023-04-24 07:16:37 -04:00
										 |  |  | #include <AK/Platform.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-13 07:50:26 -04:00
										 |  |  | #include <AK/String.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | #include <AK/URL.h>
 | 
					
						
							|  |  |  | #include <AK/Vector.h>
 | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | #include <LibCore/ArgsParser.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  | #include <LibCore/DirIterator.h>
 | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | #include <LibCore/EventLoop.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-13 07:21:49 -04:00
										 |  |  | #include <LibCore/File.h>
 | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | #include <LibCore/Timer.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-28 20:55:32 +02:00
										 |  |  | #include <LibDiff/Generator.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-22 02:35:30 +11:00
										 |  |  | #include <LibFileSystem/FileSystem.h>
 | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | #include <LibGfx/Bitmap.h>
 | 
					
						
							|  |  |  | #include <LibGfx/Font/FontDatabase.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-21 14:58:06 -04:00
										 |  |  | #include <LibGfx/ImageFormats/PNGWriter.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | #include <LibGfx/Point.h>
 | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | #include <LibGfx/Rect.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | #include <LibGfx/ShareableBitmap.h>
 | 
					
						
							|  |  |  | #include <LibGfx/Size.h>
 | 
					
						
							|  |  |  | #include <LibGfx/StandardCursor.h>
 | 
					
						
							|  |  |  | #include <LibGfx/SystemTheme.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-13 07:21:49 -04:00
										 |  |  | #include <LibIPC/File.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | #include <LibWeb/Cookie/Cookie.h>
 | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | #include <LibWeb/Cookie/ParsedCookie.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-20 18:39:20 -04:00
										 |  |  | #include <LibWeb/HTML/ActivateTab.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | #include <LibWeb/Loader/FrameLoader.h>
 | 
					
						
							|  |  |  | #include <LibWebView/ViewImplementation.h>
 | 
					
						
							|  |  |  | #include <LibWebView/WebContentClient.h>
 | 
					
						
							| 
									
										
										
										
											2022-11-21 20:03:45 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | #if !defined(AK_OS_SERENITY)
 | 
					
						
							|  |  |  | #    include <Ladybird/HelperProcess.h>
 | 
					
						
							|  |  |  | #    include <QCoreApplication>
 | 
					
						
							|  |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | class HeadlessWebContentView final : public WebView::ViewImplementation { | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | public: | 
					
						
							| 
									
										
										
										
											2023-05-06 12:46:14 +02:00
										 |  |  |     static ErrorOr<NonnullOwnPtr<HeadlessWebContentView>> create(Core::AnonymousBuffer theme, Gfx::IntSize const& window_size, StringView web_driver_ipc_path, WebView::IsLayoutTestMode is_layout_test_mode = WebView::IsLayoutTestMode::No) | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |         auto view = TRY(adopt_nonnull_own_or_enomem(new (nothrow) HeadlessWebContentView())); | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | #if defined(AK_OS_SERENITY)
 | 
					
						
							|  |  |  |         view->m_client_state.client = TRY(WebView::WebContentClient::try_create(*view)); | 
					
						
							| 
									
										
										
										
											2023-05-06 12:46:14 +02:00
										 |  |  |         (void)is_layout_test_mode; | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | #else
 | 
					
						
							|  |  |  |         auto candidate_web_content_paths = TRY(get_paths_for_helper_process("WebContent"sv)); | 
					
						
							| 
									
										
										
										
											2023-05-06 12:46:14 +02:00
										 |  |  |         view->m_client_state.client = TRY(view->launch_web_content_process(candidate_web_content_paths, WebView::EnableCallgrindProfiling::No, is_layout_test_mode)); | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2022-04-03 18:20:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |         view->client().async_update_system_theme(move(theme)); | 
					
						
							|  |  |  |         view->client().async_update_system_fonts(Gfx::FontDatabase::default_font_query(), Gfx::FontDatabase::fixed_width_font_query(), Gfx::FontDatabase::window_title_font_query()); | 
					
						
							| 
									
										
										
										
											2022-04-03 18:20:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-15 07:59:51 +02:00
										 |  |  |         view->m_viewport_rect = { { 0, 0 }, window_size }; | 
					
						
							|  |  |  |         view->client().async_set_viewport_rect(view->m_viewport_rect); | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |         view->client().async_set_window_size(window_size); | 
					
						
							| 
									
										
										
										
											2022-04-03 18:20:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-16 08:50:22 -04:00
										 |  |  |         if (!web_driver_ipc_path.is_empty()) | 
					
						
							|  |  |  |             view->client().async_connect_to_webdriver(web_driver_ipc_path); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |         return view; | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     RefPtr<Gfx::Bitmap> take_screenshot() | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |         return client().take_document_screenshot().bitmap(); | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-13 07:23:32 -04:00
										 |  |  |     ErrorOr<String> dump_layout_tree() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return String::from_deprecated_string(client().dump_layout_tree()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-28 20:54:33 +02:00
										 |  |  |     ErrorOr<String> dump_text() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return String::from_deprecated_string(client().dump_text()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-28 21:08:57 +02:00
										 |  |  |     void clear_content_filters() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         client().async_set_content_filters({}); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | private: | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     HeadlessWebContentView() = default; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     void notify_server_did_layout(Badge<WebView::WebContentClient>, Gfx::IntSize) override { } | 
					
						
							| 
									
										
										
										
											2023-05-14 18:53:29 +02:00
										 |  |  |     void notify_server_did_paint(Badge<WebView::WebContentClient>, i32, Gfx::IntSize) override { } | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     void notify_server_did_invalidate_content_rect(Badge<WebView::WebContentClient>, Gfx::IntRect const&) override { } | 
					
						
							|  |  |  |     void notify_server_did_change_selection(Badge<WebView::WebContentClient>) override { } | 
					
						
							|  |  |  |     void notify_server_did_request_cursor_change(Badge<WebView::WebContentClient>, Gfx::StandardCursor) override { } | 
					
						
							|  |  |  |     void notify_server_did_request_scroll(Badge<WebView::WebContentClient>, i32, i32) override { } | 
					
						
							|  |  |  |     void notify_server_did_request_scroll_to(Badge<WebView::WebContentClient>, Gfx::IntPoint) override { } | 
					
						
							|  |  |  |     void notify_server_did_request_scroll_into_view(Badge<WebView::WebContentClient>, Gfx::IntRect const&) override { } | 
					
						
							|  |  |  |     void notify_server_did_enter_tooltip_area(Badge<WebView::WebContentClient>, Gfx::IntPoint, DeprecatedString const&) override { } | 
					
						
							|  |  |  |     void notify_server_did_leave_tooltip_area(Badge<WebView::WebContentClient>) override { } | 
					
						
							| 
									
										
										
										
											2023-03-13 17:30:51 -04:00
										 |  |  |     void notify_server_did_request_alert(Badge<WebView::WebContentClient>, String const&) override { } | 
					
						
							|  |  |  |     void notify_server_did_request_confirm(Badge<WebView::WebContentClient>, String const&) override { } | 
					
						
							|  |  |  |     void notify_server_did_request_prompt(Badge<WebView::WebContentClient>, String const&, String const&) override { } | 
					
						
							|  |  |  |     void notify_server_did_request_set_prompt_text(Badge<WebView::WebContentClient>, String const&) override { } | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     void notify_server_did_request_accept_dialog(Badge<WebView::WebContentClient>) override { } | 
					
						
							|  |  |  |     void notify_server_did_request_dismiss_dialog(Badge<WebView::WebContentClient>) override { } | 
					
						
							| 
									
										
										
										
											2023-03-13 07:21:49 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     void notify_server_did_request_file(Badge<WebView::WebContentClient>, DeprecatedString const& path, i32 request_id) override | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         auto file = Core::File::open(path, Core::File::OpenMode::Read); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (file.is_error()) | 
					
						
							|  |  |  |             client().async_handle_file_return(file.error().code(), {}, request_id); | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |             client().async_handle_file_return(0, IPC::File(*file.value()), request_id); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     void notify_server_did_finish_handling_input_event(bool) override { } | 
					
						
							|  |  |  |     void update_zoom() override { } | 
					
						
							| 
									
										
										
										
											2023-04-15 01:04:28 +01:00
										 |  |  |     void create_client(WebView::EnableCallgrindProfiling) override { } | 
					
						
							| 
									
										
										
										
											2023-05-15 07:59:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     virtual Gfx::IntRect viewport_rect() const override { return m_viewport_rect; } | 
					
						
							| 
									
										
										
										
											2023-05-17 10:12:13 -04:00
										 |  |  |     virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const override { return widget_position; } | 
					
						
							|  |  |  |     virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const override { return content_position; } | 
					
						
							| 
									
										
										
										
											2023-05-15 07:59:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | private: | 
					
						
							|  |  |  |     Gfx::IntRect m_viewport_rect; | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | static ErrorOr<NonnullRefPtr<Core::Timer>> load_page_for_screenshot_and_exit(Core::EventLoop& event_loop, HeadlessWebContentView& view, int screenshot_timeout) | 
					
						
							| 
									
										
										
										
											2022-11-21 20:03:45 -05:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     // FIXME: Allow passing the output path as an argument.
 | 
					
						
							|  |  |  |     static constexpr auto output_file_path = "output.png"sv; | 
					
						
							| 
									
										
										
										
											2022-11-21 20:03:45 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-22 02:35:30 +11:00
										 |  |  |     if (FileSystem::exists(output_file_path)) | 
					
						
							|  |  |  |         TRY(FileSystem::remove(output_file_path, FileSystem::RecursionMode::Disallowed)); | 
					
						
							| 
									
										
										
										
											2022-11-21 20:03:45 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     outln("Taking screenshot after {} seconds", screenshot_timeout); | 
					
						
							| 
									
										
										
										
											2022-11-21 20:03:45 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     auto timer = TRY(Core::Timer::create_single_shot( | 
					
						
							|  |  |  |         screenshot_timeout * 1000, | 
					
						
							|  |  |  |         [&]() { | 
					
						
							|  |  |  |             if (auto screenshot = view.take_screenshot()) { | 
					
						
							|  |  |  |                 outln("Saving screenshot to {}", output_file_path); | 
					
						
							| 
									
										
										
										
											2022-11-21 20:03:45 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |                 auto output_file = MUST(Core::File::open(output_file_path, Core::File::OpenMode::Write)); | 
					
						
							|  |  |  |                 auto image_buffer = MUST(Gfx::PNGWriter::encode(*screenshot)); | 
					
						
							| 
									
										
										
										
											2023-03-01 17:24:50 +01:00
										 |  |  |                 MUST(output_file->write_until_depleted(image_buffer.bytes())); | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |             } else { | 
					
						
							|  |  |  |                 warnln("No screenshot available"); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-11-21 20:03:45 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |             event_loop.quit(0); | 
					
						
							|  |  |  |         })); | 
					
						
							| 
									
										
										
										
											2022-11-21 20:03:45 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     timer->start(); | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     return timer; | 
					
						
							| 
									
										
										
										
											2022-11-21 20:03:45 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-13 07:50:26 -04:00
										 |  |  | static ErrorOr<URL> format_url(StringView url) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2023-03-22 02:35:30 +11:00
										 |  |  |     if (FileSystem::exists(url)) | 
					
						
							| 
									
										
										
										
											2023-05-20 01:34:47 +02:00
										 |  |  |         return URL::create_with_file_scheme(TRY(FileSystem::real_path(url)).to_deprecated_string()); | 
					
						
							| 
									
										
										
										
											2023-03-13 07:21:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-13 07:50:26 -04:00
										 |  |  |     URL formatted_url { url }; | 
					
						
							|  |  |  |     if (!formatted_url.is_valid()) | 
					
						
							|  |  |  |         formatted_url = TRY(String::formatted("http://{}", url)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return formatted_url; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  | enum class TestMode { | 
					
						
							|  |  |  |     Layout, | 
					
						
							|  |  |  |     Text, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static ErrorOr<String> run_one_test(HeadlessWebContentView& view, StringView input_path, StringView expectation_path, TestMode mode, int timeout_in_milliseconds = 5000) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Core::EventLoop loop; | 
					
						
							|  |  |  |     bool did_timeout = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto timeout_timer = TRY(Core::Timer::create_single_shot(5000, [&] { | 
					
						
							|  |  |  |         did_timeout = true; | 
					
						
							|  |  |  |         loop.quit(0); | 
					
						
							|  |  |  |     })); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     view.load(URL::create_with_file_scheme(TRY(FileSystem::real_path(input_path)).to_deprecated_string())); | 
					
						
							|  |  |  |     (void)expectation_path; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     String result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (mode == TestMode::Layout) { | 
					
						
							|  |  |  |         view.on_load_finish = [&](auto const&) { | 
					
						
							|  |  |  |             result = view.dump_layout_tree().release_value_but_fixme_should_propagate_errors(); | 
					
						
							|  |  |  |             loop.quit(0); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } else if (mode == TestMode::Text) { | 
					
						
							|  |  |  |         view.on_load_finish = [&](auto const&) { | 
					
						
							| 
									
										
										
										
											2023-05-28 20:54:33 +02:00
										 |  |  |             result = view.dump_text().release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  |             loop.quit(0); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     timeout_timer->start(timeout_in_milliseconds); | 
					
						
							|  |  |  |     loop.exec(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (did_timeout) | 
					
						
							|  |  |  |         return Error::from_errno(ETIMEDOUT); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum class TestResult { | 
					
						
							|  |  |  |     Pass, | 
					
						
							|  |  |  |     Fail, | 
					
						
							|  |  |  |     Timeout, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static ErrorOr<TestResult> run_test(HeadlessWebContentView& view, StringView input_path, StringView expectation_path, TestMode mode) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     auto result = run_one_test(view, input_path, expectation_path, mode); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (result.is_error() && result.error().code() == ETIMEDOUT) | 
					
						
							|  |  |  |         return TestResult::Timeout; | 
					
						
							|  |  |  |     if (result.is_error()) | 
					
						
							|  |  |  |         return result.release_error(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto expectation_file = TRY(Core::File::open(expectation_path, Core::File::OpenMode::Read)); | 
					
						
							|  |  |  |     auto expectation = TRY(String::from_utf8(StringView(TRY(expectation_file->read_until_eof()).bytes()))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto actual = result.release_value(); | 
					
						
							| 
									
										
										
										
											2023-05-28 20:55:32 +02:00
										 |  |  |     auto actual_trimmed = TRY(actual.trim("\n"sv, TrimMode::Right)); | 
					
						
							|  |  |  |     auto expectation_trimmed = TRY(expectation.trim("\n"sv, TrimMode::Right)); | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-28 20:55:32 +02:00
										 |  |  |     if (actual_trimmed == expectation_trimmed) | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  |         return TestResult::Pass; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-28 20:55:32 +02:00
										 |  |  |     bool color_output = isatty(STDOUT_FILENO); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-12 11:51:28 +02:00
										 |  |  |     if (color_output) | 
					
						
							|  |  |  |         outln("\n\033[33;1mTest failed\033[0m: {}", input_path); | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |         outln("\nTest failed: {}", input_path); | 
					
						
							| 
									
										
										
										
											2023-05-28 20:55:32 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     auto hunks = Diff::from_text(expectation, actual); | 
					
						
							|  |  |  |     for (auto const& hunk : hunks) { | 
					
						
							|  |  |  |         auto original_start = hunk.original_start_line; | 
					
						
							|  |  |  |         auto target_start = hunk.target_start_line; | 
					
						
							|  |  |  |         auto num_added = hunk.added_lines.size(); | 
					
						
							|  |  |  |         auto num_removed = hunk.removed_lines.size(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         StringBuilder builder; | 
					
						
							|  |  |  |         // Source line(s)
 | 
					
						
							|  |  |  |         builder.appendff("{}", original_start); | 
					
						
							|  |  |  |         if (num_removed > 1) | 
					
						
							|  |  |  |             builder.appendff(",{}", original_start + num_removed - 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Action
 | 
					
						
							|  |  |  |         if (num_added > 0 && num_removed > 0) | 
					
						
							|  |  |  |             builder.append('c'); | 
					
						
							|  |  |  |         else if (num_added > 0) | 
					
						
							|  |  |  |             builder.append('a'); | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |             builder.append('d'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Target line(s)
 | 
					
						
							|  |  |  |         builder.appendff("{}", target_start); | 
					
						
							|  |  |  |         if (num_added > 1) | 
					
						
							|  |  |  |             builder.appendff(",{}", target_start + num_added - 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         outln("Hunk: {}", builder.string_view()); | 
					
						
							|  |  |  |         for (auto const& line : hunk.removed_lines) { | 
					
						
							|  |  |  |             if (color_output) | 
					
						
							|  |  |  |                 outln("\033[31;1m< {}\033[0m", line); | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |                 outln("< {}", line); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (num_added > 0 && num_removed > 0) | 
					
						
							|  |  |  |             outln("---"); | 
					
						
							|  |  |  |         for (auto const& line : hunk.added_lines) { | 
					
						
							|  |  |  |             if (color_output) | 
					
						
							|  |  |  |                 outln("\033[32;1m> {}\033[0m", line); | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |                 outln("> {}", line); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  |     return TestResult::Fail; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct Test { | 
					
						
							|  |  |  |     String input_path; | 
					
						
							|  |  |  |     String expectation_path; | 
					
						
							|  |  |  |     TestMode mode; | 
					
						
							|  |  |  |     Optional<TestResult> result; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static ErrorOr<void> collect_tests(Vector<Test>& tests, StringView path, StringView trail, TestMode mode) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     Core::DirIterator it(TRY(String::formatted("{}/input/{}", path, trail)).to_deprecated_string(), Core::DirIterator::Flags::SkipDots); | 
					
						
							|  |  |  |     while (it.has_next()) { | 
					
						
							|  |  |  |         auto name = it.next_path(); | 
					
						
							|  |  |  |         auto input_path = TRY(FileSystem::real_path(TRY(String::formatted("{}/input/{}/{}", path, trail, name)))); | 
					
						
							|  |  |  |         if (FileSystem::is_directory(input_path)) { | 
					
						
							|  |  |  |             TRY(collect_tests(tests, path, TRY(String::formatted("{}/{}", trail, name)), mode)); | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (!name.ends_with(".html"sv)) | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         auto basename = LexicalPath::title(name); | 
					
						
							|  |  |  |         auto expectation_path = TRY(String::formatted("{}/expected/{}/{}.txt", path, trail, basename)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         tests.append({ move(input_path), move(expectation_path), mode, {} }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static ErrorOr<int> run_tests(HeadlessWebContentView& view, StringView test_root_path) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2023-05-28 21:08:57 +02:00
										 |  |  |     view.clear_content_filters(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  |     Vector<Test> tests; | 
					
						
							|  |  |  |     TRY(collect_tests(tests, TRY(String::formatted("{}/Layout", test_root_path)), "."sv, TestMode::Layout)); | 
					
						
							|  |  |  |     TRY(collect_tests(tests, TRY(String::formatted("{}/Text", test_root_path)), "."sv, TestMode::Text)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     size_t pass_count = 0; | 
					
						
							|  |  |  |     size_t fail_count = 0; | 
					
						
							|  |  |  |     size_t timeout_count = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bool is_tty = isatty(STDOUT_FILENO); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     outln("Running {} tests...", tests.size()); | 
					
						
							|  |  |  |     for (size_t i = 0; i < tests.size(); ++i) { | 
					
						
							|  |  |  |         auto& test = tests[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (is_tty) { | 
					
						
							|  |  |  |             // Keep clearing and reusing the same line if stdout is a TTY.
 | 
					
						
							|  |  |  |             out("\33[2K\r"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         out("{}/{}: {}", i + 1, tests.size(), LexicalPath::relative_path(test.input_path, test_root_path)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-28 08:02:55 +02:00
										 |  |  |         if (is_tty) | 
					
						
							|  |  |  |             fflush(stdout); | 
					
						
							|  |  |  |         else | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  |             outln(""); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         test.result = TRY(run_test(view, test.input_path, test.expectation_path, test.mode)); | 
					
						
							|  |  |  |         switch (*test.result) { | 
					
						
							|  |  |  |         case TestResult::Pass: | 
					
						
							|  |  |  |             ++pass_count; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case TestResult::Fail: | 
					
						
							|  |  |  |             ++fail_count; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case TestResult::Timeout: | 
					
						
							|  |  |  |             ++timeout_count; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (is_tty) | 
					
						
							|  |  |  |         outln("\33[2K\rDone!"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     outln("=================================================="); | 
					
						
							|  |  |  |     outln("Pass: {}, Fail: {}, Timeout: {}", pass_count, fail_count, timeout_count); | 
					
						
							|  |  |  |     outln("=================================================="); | 
					
						
							|  |  |  |     for (auto& test : tests) { | 
					
						
							|  |  |  |         if (*test.result == TestResult::Pass) | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         outln("{}: {}", *test.result == TestResult::Fail ? "Fail" : "Timeout", test.input_path); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (timeout_count == 0 && fail_count == 0) | 
					
						
							|  |  |  |         return 0; | 
					
						
							|  |  |  |     return 1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | ErrorOr<int> serenity_main(Main::Arguments arguments) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  | #if !defined(AK_OS_SERENITY)
 | 
					
						
							|  |  |  |     QCoreApplication app(arguments.argc, arguments.argv); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |     Core::EventLoop event_loop; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     int screenshot_timeout = 1; | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  |     StringView url; | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     auto resources_folder = "/res"sv; | 
					
						
							|  |  |  |     StringView web_driver_ipc_path; | 
					
						
							| 
									
										
										
										
											2023-03-13 07:23:32 -04:00
										 |  |  |     bool dump_layout_tree = false; | 
					
						
							| 
									
										
										
										
											2023-05-25 19:37:30 +02:00
										 |  |  |     bool dump_text = false; | 
					
						
							| 
									
										
										
										
											2023-05-06 12:46:14 +02:00
										 |  |  |     bool is_layout_test_mode = false; | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  |     StringView test_root_path; | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     Core::ArgsParser args_parser; | 
					
						
							|  |  |  |     args_parser.set_general_help("This utility runs the Browser in headless mode."); | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     args_parser.add_option(screenshot_timeout, "Take a screenshot after [n] seconds (default: 1)", "screenshot", 's', "n"); | 
					
						
							| 
									
										
										
										
											2023-03-13 07:23:32 -04:00
										 |  |  |     args_parser.add_option(dump_layout_tree, "Dump layout tree and exit", "dump-layout-tree", 'd'); | 
					
						
							| 
									
										
										
										
											2023-05-25 19:37:30 +02:00
										 |  |  |     args_parser.add_option(dump_text, "Dump text and exit", "dump-text", 'T'); | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  |     args_parser.add_option(test_root_path, "Run tests in path", "run-tests", 'R', "test-root-path"); | 
					
						
							| 
									
										
										
										
											2022-07-18 20:23:19 +02:00
										 |  |  |     args_parser.add_option(resources_folder, "Path of the base resources folder (defaults to /res)", "resources", 'r', "resources-root-path"); | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     args_parser.add_option(web_driver_ipc_path, "Path to the WebDriver IPC socket", "webdriver-ipc-path", 0, "path"); | 
					
						
							| 
									
										
										
										
											2023-05-06 12:46:14 +02:00
										 |  |  |     args_parser.add_option(is_layout_test_mode, "Enable layout test mode", "layout-test-mode", 0); | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  |     args_parser.add_positional_argument(url, "URL to open", "url", Core::ArgsParser::Required::No); | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  |     args_parser.parse(arguments); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Gfx::FontDatabase::set_default_font_query("Katica 10 400 0"); | 
					
						
							| 
									
										
										
										
											2022-07-31 18:41:07 +02:00
										 |  |  |     Gfx::FontDatabase::set_window_title_font_query("Katica 10 700 0"); | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  |     Gfx::FontDatabase::set_fixed_width_font_query("Csilla 10 400 0"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     auto fonts_path = LexicalPath::join(resources_folder, "fonts"sv); | 
					
						
							|  |  |  |     Gfx::FontDatabase::set_default_fonts_lookup_path(fonts_path.string()); | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     auto theme_path = LexicalPath::join(resources_folder, "themes"sv, "Default.ini"sv); | 
					
						
							|  |  |  |     auto theme = TRY(Gfx::load_system_theme(theme_path.string())); | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     // FIXME: Allow passing the window size as an argument.
 | 
					
						
							|  |  |  |     static constexpr Gfx::IntSize window_size { 800, 600 }; | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  |     if (!test_root_path.is_empty()) { | 
					
						
							|  |  |  |         // --run-tests implies --layout-test-mode.
 | 
					
						
							|  |  |  |         is_layout_test_mode = true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-06 12:46:14 +02:00
										 |  |  |     auto view = TRY(HeadlessWebContentView::create(move(theme), window_size, web_driver_ipc_path, is_layout_test_mode ? WebView::IsLayoutTestMode::Yes : WebView::IsLayoutTestMode::No)); | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |     RefPtr<Core::Timer> timer; | 
					
						
							| 
									
										
										
										
											2023-03-13 07:23:32 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-27 19:46:17 +02:00
										 |  |  |     if (!test_root_path.is_empty()) { | 
					
						
							|  |  |  |         return run_tests(*view, test_root_path); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-13 07:23:32 -04:00
										 |  |  |     if (dump_layout_tree) { | 
					
						
							|  |  |  |         view->on_load_finish = [&](auto const&) { | 
					
						
							|  |  |  |             auto layout_tree = view->dump_layout_tree().release_value_but_fixme_should_propagate_errors(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-25 12:09:34 +01:00
										 |  |  |             out("{}", layout_tree); | 
					
						
							| 
									
										
										
										
											2023-03-13 07:23:32 -04:00
										 |  |  |             fflush(stdout); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-25 19:37:30 +02:00
										 |  |  |             event_loop.quit(0); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } else if (dump_text) { | 
					
						
							|  |  |  |         view->on_load_finish = [&](auto const&) { | 
					
						
							| 
									
										
										
										
											2023-05-28 20:54:33 +02:00
										 |  |  |             auto text = view->dump_text().release_value_but_fixme_should_propagate_errors(); | 
					
						
							| 
									
										
										
										
											2023-05-25 19:37:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             out("{}", text); | 
					
						
							|  |  |  |             fflush(stdout); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-13 07:23:32 -04:00
										 |  |  |             event_loop.quit(0); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } else if (web_driver_ipc_path.is_empty()) { | 
					
						
							| 
									
										
										
										
											2023-03-12 12:38:15 -04:00
										 |  |  |         timer = TRY(load_page_for_screenshot_and_exit(event_loop, *view, screenshot_timeout)); | 
					
						
							| 
									
										
										
										
											2023-03-13 07:23:32 -04:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-13 07:23:32 -04:00
										 |  |  |     view->load(TRY(format_url(url))); | 
					
						
							| 
									
										
										
										
											2022-04-03 18:15:36 +02:00
										 |  |  |     return event_loop.exec(); | 
					
						
							|  |  |  | } |