diff --git a/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp b/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp index 3462b3d68cf..14d5b899ba4 100644 --- a/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/IPCCompiler/main.cpp @@ -71,7 +71,7 @@ static bool is_primitive_type(ByteString const& type) static bool is_simple_type(ByteString const& type) { // Small types that it makes sense just to pass by value. - return type.is_one_of("AK::CaseSensitivity", "Gfx::Color", "Web::DevicePixels", "Gfx::IntPoint", "Gfx::FloatPoint", "Web::DevicePixelPoint", "Gfx::IntSize", "Gfx::FloatSize", "Web::DevicePixelSize", "Core::File::OpenMode", "Web::Cookie::Source", "Web::EventResult", "Web::HTML::AllowMultipleFiles", "Web::HTML::AudioPlayState", "Web::HTML::HistoryHandlingBehavior"); + return type.is_one_of("AK::CaseSensitivity", "Gfx::Color", "Web::DevicePixels", "Gfx::IntPoint", "Gfx::FloatPoint", "Web::DevicePixelPoint", "Gfx::IntSize", "Gfx::FloatSize", "Web::DevicePixelSize", "Core::File::OpenMode", "Web::Cookie::Source", "Web::EventResult", "Web::HTML::AllowMultipleFiles", "Web::HTML::AudioPlayState", "Web::HTML::HistoryHandlingBehavior", "WebView::PageInfoType"); } static bool is_primitive_or_simple_type(ByteString const& type) diff --git a/Userland/Libraries/LibWebView/PageInfo.h b/Userland/Libraries/LibWebView/PageInfo.h new file mode 100644 index 00000000000..25f6ea6649d --- /dev/null +++ b/Userland/Libraries/LibWebView/PageInfo.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace WebView { + +enum class PageInfoType { + Text = 1 << 0, + LayoutTree = 1 << 2, + PaintTree = 1 << 3, + GCGraph = 1 << 4, +}; + +AK_ENUM_BITWISE_OPERATORS(PageInfoType); + +} diff --git a/Userland/Libraries/LibWebView/ViewImplementation.cpp b/Userland/Libraries/LibWebView/ViewImplementation.cpp index d143874666a..b837588670a 100644 --- a/Userland/Libraries/LibWebView/ViewImplementation.cpp +++ b/Userland/Libraries/LibWebView/ViewImplementation.cpp @@ -583,15 +583,40 @@ void ViewImplementation::did_receive_screenshot(Badge, Gfx::Sh m_pending_screenshot = nullptr; } +NonnullRefPtr> ViewImplementation::request_internal_page_info(PageInfoType type) +{ + auto promise = Core::Promise::construct(); + + if (m_pending_info_request) { + // For simplicitly, only allow one info request at a time for now. + promise->reject(Error::from_string_literal("A page info request is already in progress")); + return promise; + } + + m_pending_info_request = promise; + client().async_request_internal_page_info(page_id(), type); + + return promise; +} + +void ViewImplementation::did_receive_internal_page_info(Badge, PageInfoType, String const& info) +{ + VERIFY(m_pending_info_request); + + m_pending_info_request->resolve(String { info }); + m_pending_info_request = nullptr; +} + ErrorOr ViewImplementation::dump_gc_graph() { - auto gc_graph_json = client().dump_gc_graph(page_id()); + auto promise = request_internal_page_info(PageInfoType::GCGraph); + auto gc_graph_json = TRY(promise->await()); LexicalPath path { Core::StandardPaths::tempfile_directory() }; path = path.append(TRY(Core::DateTime::now().to_string("gc-graph-%Y-%m-%d-%H-%M-%S.json"sv))); - auto screenshot_file = TRY(Core::File::open(path.string(), Core::File::OpenMode::Write)); - TRY(screenshot_file->write_until_depleted(gc_graph_json.bytes())); + auto dump_file = TRY(Core::File::open(path.string(), Core::File::OpenMode::Write)); + TRY(dump_file->write_until_depleted(gc_graph_json.bytes())); return path; } diff --git a/Userland/Libraries/LibWebView/ViewImplementation.h b/Userland/Libraries/LibWebView/ViewImplementation.h index 5ce56eb0c43..ac2c736efae 100644 --- a/Userland/Libraries/LibWebView/ViewImplementation.h +++ b/Userland/Libraries/LibWebView/ViewImplementation.h @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace WebView { @@ -140,6 +141,9 @@ public: NonnullRefPtr> take_dom_node_screenshot(i32); virtual void did_receive_screenshot(Badge, Gfx::ShareableBitmap const&); + NonnullRefPtr> request_internal_page_info(PageInfoType); + void did_receive_internal_page_info(Badge, PageInfoType, String const&); + ErrorOr dump_gc_graph(); void set_user_style_sheet(String source); @@ -286,6 +290,7 @@ protected: RefPtr m_repeated_crash_timer; RefPtr> m_pending_screenshot; + RefPtr> m_pending_info_request; Web::HTML::AudioPlayState m_audio_play_state { Web::HTML::AudioPlayState::Paused }; size_t m_number_of_elements_playing_audio { 0 }; diff --git a/Userland/Libraries/LibWebView/WebContentClient.cpp b/Userland/Libraries/LibWebView/WebContentClient.cpp index eeb128c1e14..14245b9332f 100644 --- a/Userland/Libraries/LibWebView/WebContentClient.cpp +++ b/Userland/Libraries/LibWebView/WebContentClient.cpp @@ -342,6 +342,12 @@ void WebContentClient::did_take_screenshot(u64 page_id, Gfx::ShareableBitmap con view->did_receive_screenshot({}, screenshot); } +void WebContentClient::did_get_internal_page_info(u64 page_id, WebView::PageInfoType type, String const& info) +{ + if (auto view = view_for_page_id(page_id); view.has_value()) + view->did_receive_internal_page_info({}, type, info); +} + void WebContentClient::did_output_js_console_message(u64 page_id, i32 message_index) { if (auto view = view_for_page_id(page_id); view.has_value()) { diff --git a/Userland/Libraries/LibWebView/WebContentClient.h b/Userland/Libraries/LibWebView/WebContentClient.h index 767e0044b1e..6c8a3e9cb2a 100644 --- a/Userland/Libraries/LibWebView/WebContentClient.h +++ b/Userland/Libraries/LibWebView/WebContentClient.h @@ -78,6 +78,7 @@ private: virtual void did_finish_editing_dom_node(u64 page_id, Optional const& node_id) override; virtual void did_get_dom_node_html(u64 page_id, String const& html) override; virtual void did_take_screenshot(u64 page_id, Gfx::ShareableBitmap const& screenshot) override; + virtual void did_get_internal_page_info(u64 page_id, PageInfoType, String const&) override; virtual void did_output_js_console_message(u64 page_id, i32 message_index) override; virtual void did_get_js_console_messages(u64 page_id, i32 start_index, Vector const& message_types, Vector const& messages) override; virtual void did_change_favicon(u64 page_id, Gfx::ShareableBitmap const&) override; diff --git a/Userland/Services/WebContent/ConnectionFromClient.cpp b/Userland/Services/WebContent/ConnectionFromClient.cpp index 29cea79994c..879858ac3f9 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.cpp +++ b/Userland/Services/WebContent/ConnectionFromClient.cpp @@ -840,10 +840,104 @@ void ConnectionFromClient::take_dom_node_screenshot(u64 page_id, i32 node_id) page->queue_screenshot_task(node_id); } -Messages::WebContentServer::DumpGcGraphResponse ConnectionFromClient::dump_gc_graph(u64) +static void append_page_text(Web::Page& page, StringBuilder& builder) { - auto gc_graph_json = Web::Bindings::main_thread_vm().heap().dump_graph(); - return MUST(String::from_byte_string(gc_graph_json.to_byte_string())); + auto* document = page.top_level_browsing_context().active_document(); + if (!document) { + builder.append("(no DOM tree)"sv); + return; + } + + auto* body = document->body(); + if (!body) { + builder.append("(no body)"sv); + return; + } + + builder.append(body->inner_text()); +} + +static void append_layout_tree(Web::Page& page, StringBuilder& builder) +{ + auto* document = page.top_level_browsing_context().active_document(); + if (!document) { + builder.append("(no DOM tree)"sv); + return; + } + + document->update_layout(); + + auto* layout_root = document->layout_node(); + if (!layout_root) { + builder.append("(no layout tree)"sv); + return; + } + + Web::dump_tree(builder, *layout_root); +} + +static void append_paint_tree(Web::Page& page, StringBuilder& builder) +{ + auto* document = page.top_level_browsing_context().active_document(); + if (!document) { + builder.append("(no DOM tree)"sv); + return; + } + + document->update_layout(); + + auto* layout_root = document->layout_node(); + if (!layout_root) { + builder.append("(no layout tree)"sv); + return; + } + if (!layout_root->paintable()) { + builder.append("(no paint tree)"sv); + return; + } + + Web::dump_tree(builder, *layout_root->paintable()); +} + +static void append_gc_graph(StringBuilder& builder) +{ + auto gc_graph = Web::Bindings::main_thread_vm().heap().dump_graph(); + gc_graph.serialize(builder); +} + +void ConnectionFromClient::request_internal_page_info(u64 page_id, WebView::PageInfoType type) +{ + auto page = this->page(page_id); + if (!page.has_value()) { + async_did_get_internal_page_info(page_id, type, "(no page)"_string); + return; + } + + StringBuilder builder; + + if (has_flag(type, WebView::PageInfoType::Text)) { + append_page_text(page->page(), builder); + } + + if (has_flag(type, WebView::PageInfoType::LayoutTree)) { + if (!builder.is_empty()) + builder.append("\n"sv); + append_layout_tree(page->page(), builder); + } + + if (has_flag(type, WebView::PageInfoType::PaintTree)) { + if (!builder.is_empty()) + builder.append("\n"sv); + append_paint_tree(page->page(), builder); + } + + if (has_flag(type, WebView::PageInfoType::GCGraph)) { + if (!builder.is_empty()) + builder.append("\n"sv); + append_gc_graph(builder); + } + + async_did_get_internal_page_info(page_id, type, MUST(builder.to_string())); } Messages::WebContentServer::GetSelectedTextResponse ConnectionFromClient::get_selected_text(u64 page_id) @@ -895,58 +989,6 @@ void ConnectionFromClient::paste(u64 page_id, String const& text) page->page().focused_navigable().paste(text); } -Messages::WebContentServer::DumpLayoutTreeResponse ConnectionFromClient::dump_layout_tree(u64 page_id) -{ - auto page = this->page(page_id); - if (!page.has_value()) - return ByteString { "(no page)" }; - - auto* document = page->page().top_level_browsing_context().active_document(); - if (!document) - return ByteString { "(no DOM tree)" }; - document->update_layout(); - auto* layout_root = document->layout_node(); - if (!layout_root) - return ByteString { "(no layout tree)" }; - StringBuilder builder; - Web::dump_tree(builder, *layout_root); - return builder.to_byte_string(); -} - -Messages::WebContentServer::DumpPaintTreeResponse ConnectionFromClient::dump_paint_tree(u64 page_id) -{ - auto page = this->page(page_id); - if (!page.has_value()) - return ByteString { "(no page)" }; - - auto* document = page->page().top_level_browsing_context().active_document(); - if (!document) - return ByteString { "(no DOM tree)" }; - document->update_layout(); - auto* layout_root = document->layout_node(); - if (!layout_root) - return ByteString { "(no layout tree)" }; - if (!layout_root->paintable()) - return ByteString { "(no paint tree)" }; - StringBuilder builder; - Web::dump_tree(builder, *layout_root->paintable()); - return builder.to_byte_string(); -} - -Messages::WebContentServer::DumpTextResponse ConnectionFromClient::dump_text(u64 page_id) -{ - auto page = this->page(page_id); - if (!page.has_value()) - return ByteString { "(no page)" }; - - auto* document = page->page().top_level_browsing_context().active_document(); - if (!document) - return ByteString { "(no DOM tree)" }; - if (!document->body()) - return ByteString { "(no body)" }; - return document->body()->inner_text(); -} - void ConnectionFromClient::set_content_filters(u64, Vector const& filters) { Web::ContentFilter::the().set_patterns(filters).release_value_but_fixme_should_propagate_errors(); diff --git a/Userland/Services/WebContent/ConnectionFromClient.h b/Userland/Services/WebContent/ConnectionFromClient.h index 9bbc18a5b95..d7882ced285 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.h +++ b/Userland/Services/WebContent/ConnectionFromClient.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -90,9 +91,6 @@ private: virtual void remove_dom_node(u64 page_id, i32 node_id) override; virtual void get_dom_node_html(u64 page_id, i32 node_id) override; - virtual Messages::WebContentServer::DumpLayoutTreeResponse dump_layout_tree(u64 page_id) override; - virtual Messages::WebContentServer::DumpPaintTreeResponse dump_paint_tree(u64 page_id) override; - virtual Messages::WebContentServer::DumpTextResponse dump_text(u64 page_id) override; virtual void set_content_filters(u64 page_id, Vector const&) override; virtual void set_autoplay_allowed_on_all_websites(u64 page_id) override; virtual void set_autoplay_allowlist(u64 page_id, Vector const& allowlist) override; @@ -135,7 +133,7 @@ private: virtual void take_document_screenshot(u64 page_id) override; virtual void take_dom_node_screenshot(u64 page_id, i32 node_id) override; - virtual Messages::WebContentServer::DumpGcGraphResponse dump_gc_graph(u64 page_id) override; + virtual void request_internal_page_info(u64 page_id, WebView::PageInfoType) override; virtual Messages::WebContentServer::GetLocalStorageEntriesResponse get_local_storage_entries(u64 page_id) override; virtual Messages::WebContentServer::GetSessionStorageEntriesResponse get_session_storage_entries(u64 page_id) override; diff --git a/Userland/Services/WebContent/WebContentClient.ipc b/Userland/Services/WebContent/WebContentClient.ipc index 31c65c6cba0..3a5b8fed2b8 100644 --- a/Userland/Services/WebContent/WebContentClient.ipc +++ b/Userland/Services/WebContent/WebContentClient.ipc @@ -16,6 +16,7 @@ #include #include #include +#include endpoint WebContentClient { @@ -62,6 +63,8 @@ endpoint WebContentClient did_take_screenshot(u64 page_id, Gfx::ShareableBitmap screenshot) =| + did_get_internal_page_info(u64 page_id, WebView::PageInfoType type, String info) =| + did_change_favicon(u64 page_id, Gfx::ShareableBitmap favicon) =| did_request_all_cookies(u64 page_id, URL::URL url) => (Vector cookies) did_request_named_cookie(u64 page_id, URL::URL url, String name) => (Optional cookie) diff --git a/Userland/Services/WebContent/WebContentServer.ipc b/Userland/Services/WebContent/WebContentServer.ipc index 10e0f15292e..a49198e2a46 100644 --- a/Userland/Services/WebContent/WebContentServer.ipc +++ b/Userland/Services/WebContent/WebContentServer.ipc @@ -11,6 +11,7 @@ #include #include #include +#include endpoint WebContentServer { @@ -62,14 +63,10 @@ endpoint WebContentServer take_document_screenshot(u64 page_id) =| take_dom_node_screenshot(u64 page_id, i32 node_id) =| - dump_gc_graph(u64 page_id) => (String json) + request_internal_page_info(u64 page_id, WebView::PageInfoType type) =| run_javascript(u64 page_id, ByteString js_source) =| - dump_layout_tree(u64 page_id) => (ByteString dump) - dump_paint_tree(u64 page_id) => (ByteString dump) - dump_text(u64 page_id) => (ByteString dump) - get_selected_text(u64 page_id) => (ByteString selection) select_all(u64 page_id) =| paste(u64 page_id, String text) =| diff --git a/Userland/Utilities/headless-browser.cpp b/Userland/Utilities/headless-browser.cpp index 2996be3ae3a..2f0311688a0 100644 --- a/Userland/Utilities/headless-browser.cpp +++ b/Userland/Utilities/headless-browser.cpp @@ -120,21 +120,6 @@ public: m_pending_screenshot->resolve(screenshot.bitmap()); } - ErrorOr dump_layout_tree() - { - return String::from_byte_string(client().dump_layout_tree(0)); - } - - ErrorOr dump_paint_tree() - { - return String::from_byte_string(client().dump_paint_tree(0)); - } - - ErrorOr dump_text() - { - return String::from_byte_string(client().dump_text(0)); - } - void clear_content_filters() { client().async_set_content_filters(0, {}); @@ -256,18 +241,15 @@ static ErrorOr run_dump_test(HeadlessWebContentView& view, URL::URL // It also causes a lot more code to run, which is good for finding bugs. :^) (void)view.take_screenshot(); - StringBuilder builder; - builder.append(view.dump_layout_tree().release_value_but_fixme_should_propagate_errors()); - builder.append("\n"sv); - builder.append(view.dump_paint_tree().release_value_but_fixme_should_propagate_errors()); - result = builder.to_string().release_value_but_fixme_should_propagate_errors(); + auto promise = view.request_internal_page_info(WebView::PageInfoType::LayoutTree | WebView::PageInfoType::PaintTree); + result = MUST(promise->await()); loop.quit(0); } }; + view.on_text_test_finish = {}; } else if (mode == TestMode::Text) { - view.on_load_finish = [&](auto const& loaded_url) { // NOTE: We don't want subframe loads to trigger the test finish. if (!url.equals(loaded_url, URL::ExcludeFragment::Yes)) @@ -276,8 +258,11 @@ static ErrorOr run_dump_test(HeadlessWebContentView& view, URL::URL if (did_finish_test) loop.quit(0); }; + view.on_text_test_finish = [&]() { - result = view.dump_text().release_value_but_fixme_should_propagate_errors(); + auto promise = view.request_internal_page_info(WebView::PageInfoType::Text); + result = MUST(promise->await()); + did_finish_test = true; if (did_finish_loading) loop.quit(0);