From 3ec5c1941f25041c6bf073f2940f6035e084a4e2 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 19 Aug 2024 12:11:39 -0400 Subject: [PATCH] LibWeb+LibWebView: Add a button to the Inspector to export its contents When working on the Inspector's HTML, it's often kind of tricky to debug when an element is styled / positioned incorrectly. We don't have a way to inspect the Inspector itself. This adds a button to the Inspector to export its HTML/CSS/JS contents to the downloads directory. This allows for more easily testing changes, especially by opening the exported HTML in another browser's dev tools. We will ultimately likely remove this button (or make it hidden) by the time we are production-ready. But it's quite useful for now. --- Base/res/icons/16x16/download.png | Bin 0 -> 778 bytes Base/res/ladybird/inspector.css | 26 ++++++- Base/res/ladybird/inspector.js | 5 ++ Ladybird/cmake/ResourceFiles.cmake | 1 + .../Libraries/LibWeb/Internals/Inspector.cpp | 7 +- .../Libraries/LibWeb/Internals/Inspector.h | 4 +- .../Libraries/LibWeb/Internals/Inspector.idl | 2 + Userland/Libraries/LibWeb/Page/Page.h | 1 + .../Libraries/LibWebView/InspectorClient.cpp | 69 ++++++++++++++++-- .../Libraries/LibWebView/ViewImplementation.h | 1 + .../Libraries/LibWebView/WebContentClient.cpp | 8 ++ .../Libraries/LibWebView/WebContentClient.h | 1 + Userland/Services/WebContent/PageClient.cpp | 5 ++ Userland/Services/WebContent/PageClient.h | 1 + .../Services/WebContent/WebContentClient.ipc | 1 + 15 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 Base/res/icons/16x16/download.png diff --git a/Base/res/icons/16x16/download.png b/Base/res/icons/16x16/download.png new file mode 100644 index 0000000000000000000000000000000000000000..7836d3c34114d93642ea4d404d2df78009f22157 GIT binary patch literal 778 zcmV+l1NHogP)EX>4Tx04R}tkv&MmP!xqvQ>9fZ4i*$~$WV2$AS&XhRVYG*P%E_RVDi#GXws0R zxHt-~1qXi?s}3&Cx;nTDg5VE`qmz@OiFVkWr1gg%~W!pI7}?&TUcpfRxmZ69;| zJytnyan?!|*0?8sVJNGwEOVXK5aL+G5+sOFP(cwT*oe}ulVTxB=dm{aLDw&lOCeVg zj2sInLxb%4!T;d*Y^~hngqIYG0i7?7^Dztrc7b};alVfor+xzXpMfjA=`YuSnNQNI zO)YW+^lk$e*G)~?11@)ffhR*YWmodk5^_1<{fxdT1N7Yj-D}?58s|8D08%uo#0_w8 z2#n+@d%exOgROJ>x2G|`A2#Q5kA!54?*IS*24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&R~0xC3#HuO>e000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0002tNklxD-XWc@Mw=D7#KPs$1|! z`~w0p7A}rs5o5%Hir>^9}<3kM*D=nR~8% zeuaEJ$tR { window.scrollTo(0, position); }; +inspector.exportInspector = () => { + const html = document.documentElement.outerHTML; + inspector.exportInspectorHTML(html); +}; + inspector.reset = () => { let domTree = document.getElementById("dom-tree"); domTree.innerHTML = ""; diff --git a/Ladybird/cmake/ResourceFiles.cmake b/Ladybird/cmake/ResourceFiles.cmake index ff371ffcec..6f383a4e98 100644 --- a/Ladybird/cmake/ResourceFiles.cmake +++ b/Ladybird/cmake/ResourceFiles.cmake @@ -11,6 +11,7 @@ set(16x16_ICONS audio-volume-high.png audio-volume-muted.png close-tab.png + download.png edit-copy.png filetype-css.png filetype-folder-open.png diff --git a/Userland/Libraries/LibWeb/Internals/Inspector.cpp b/Userland/Libraries/LibWeb/Internals/Inspector.cpp index 761f2c80f9..97505b1438 100644 --- a/Userland/Libraries/LibWeb/Internals/Inspector.cpp +++ b/Userland/Libraries/LibWeb/Internals/Inspector.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -76,4 +76,9 @@ void Inspector::execute_console_script(String const& script) global_object().browsing_context()->page().client().inspector_did_execute_console_script(script); } +void Inspector::export_inspector_html(String const& html) +{ + global_object().browsing_context()->page().client().inspector_did_export_inspector_html(html); +} + } diff --git a/Userland/Libraries/LibWeb/Internals/Inspector.h b/Userland/Libraries/LibWeb/Internals/Inspector.h index 0058984d72..faf1a08636 100644 --- a/Userland/Libraries/LibWeb/Internals/Inspector.h +++ b/Userland/Libraries/LibWeb/Internals/Inspector.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -31,6 +31,8 @@ public: void execute_console_script(String const& script); + void export_inspector_html(String const& html); + private: explicit Inspector(JS::Realm&); diff --git a/Userland/Libraries/LibWeb/Internals/Inspector.idl b/Userland/Libraries/LibWeb/Internals/Inspector.idl index a87479bb35..d31b2b5dec 100644 --- a/Userland/Libraries/LibWeb/Internals/Inspector.idl +++ b/Userland/Libraries/LibWeb/Internals/Inspector.idl @@ -14,4 +14,6 @@ undefined executeConsoleScript(DOMString script); + undefined exportInspectorHTML(DOMString html); + }; diff --git a/Userland/Libraries/LibWeb/Page/Page.h b/Userland/Libraries/LibWeb/Page/Page.h index 55cb6d6ea2..30c10b9c07 100644 --- a/Userland/Libraries/LibWeb/Page/Page.h +++ b/Userland/Libraries/LibWeb/Page/Page.h @@ -377,6 +377,7 @@ public: virtual void inspector_did_replace_dom_node_attribute([[maybe_unused]] i32 node_id, [[maybe_unused]] size_t attribute_index, [[maybe_unused]] JS::NonnullGCPtr replacement_attributes) { } virtual void inspector_did_request_dom_tree_context_menu([[maybe_unused]] i32 node_id, [[maybe_unused]] CSSPixelPoint position, [[maybe_unused]] String const& type, [[maybe_unused]] Optional const& tag, [[maybe_unused]] Optional const& attribute_index) { } virtual void inspector_did_execute_console_script([[maybe_unused]] String const& script) { } + virtual void inspector_did_export_inspector_html([[maybe_unused]] String const& html) { } virtual void schedule_repaint() = 0; virtual bool is_ready_to_paint() const = 0; diff --git a/Userland/Libraries/LibWebView/InspectorClient.cpp b/Userland/Libraries/LibWebView/InspectorClient.cpp index 923e58af41..74ddc0e883 100644 --- a/Userland/Libraries/LibWebView/InspectorClient.cpp +++ b/Userland/Libraries/LibWebView/InspectorClient.cpp @@ -7,7 +7,12 @@ #include #include #include +#include #include +#include +#include +#include +#include #include #include #include @@ -15,6 +20,9 @@ namespace WebView { +static constexpr auto INSPECTOR_CSS = "resource://ladybird/inspector.css"sv; +static constexpr auto INSPECTOR_JS = "resource://ladybird/inspector.js"sv; + static ErrorOr parse_json_tree(StringView json) { auto parsed_tree = TRY(JsonValue::from_string(json)); @@ -179,6 +187,47 @@ InspectorClient::InspectorClient(ViewImplementation& content_web_view, ViewImple m_content_web_view.js_console_input(script.to_byte_string()); }; + m_inspector_web_view.on_inspector_exported_inspector_html = [this](String const& html) { + auto inspector_path = LexicalPath::join(Core::StandardPaths::downloads_directory(), "inspector"sv); + + if (auto result = Core::Directory::create(inspector_path, Core::Directory::CreateDirectories::Yes); result.is_error()) { + append_console_warning(MUST(String::formatted("Unable to create {}: {}", inspector_path, result.error()))); + return; + } + + auto export_file = [&](auto name, auto const& contents) { + auto path = inspector_path.append(name); + + auto file = Core::File::open(path.string(), Core::File::OpenMode::Write); + if (file.is_error()) { + append_console_warning(MUST(String::formatted("Unable to open {}: {}", path, file.error()))); + return false; + } + + if (auto result = file.value()->write_until_depleted(contents); result.is_error()) { + append_console_warning(MUST(String::formatted("Unable to save {}: {}", path, result.error()))); + return false; + } + + return true; + }; + + auto inspector_css = MUST(Core::Resource::load_from_uri(INSPECTOR_CSS)); + auto inspector_js = MUST(Core::Resource::load_from_uri(INSPECTOR_JS)); + + auto inspector_html = MUST(html.replace(INSPECTOR_CSS, "inspector.css"sv, ReplaceMode::All)); + inspector_html = MUST(inspector_html.replace(INSPECTOR_JS, "inspector.js"sv, ReplaceMode::All)); + + if (!export_file("inspector.html"sv, inspector_html)) + return; + if (!export_file("inspector.css"sv, inspector_css->data())) + return; + if (!export_file("inspector.js"sv, inspector_js->data())) + return; + + append_console_message(MUST(String::formatted("Exported Inspector files to {}", inspector_path))); + }; + load_inspector(); } @@ -359,18 +408,22 @@ void InspectorClient::load_inspector() builder.append(HTML_HIGHLIGHTER_STYLE); - builder.append(R"~~~( + builder.appendff(R"~~~( - +
+
+
+ +
@@ -384,6 +437,7 @@ void InspectorClient::load_inspector()
+
@@ -391,6 +445,7 @@ void InspectorClient::load_inspector()
+
@@ -402,7 +457,8 @@ void InspectorClient::load_inspector()
-)~~~"sv); +)~~~", + INSPECTOR_CSS); auto generate_property_table = [&](auto name) { builder.appendff(R"~~~( @@ -435,14 +491,15 @@ void InspectorClient::load_inspector()
)~~~"sv); - builder.append(R"~~~( + builder.appendff(R"~~~( - + -)~~~"sv); +)~~~", + INSPECTOR_JS); m_inspector_web_view.load_html(builder.string_view()); } diff --git a/Userland/Libraries/LibWebView/ViewImplementation.h b/Userland/Libraries/LibWebView/ViewImplementation.h index dea99dd9e0..629f249fc7 100644 --- a/Userland/Libraries/LibWebView/ViewImplementation.h +++ b/Userland/Libraries/LibWebView/ViewImplementation.h @@ -218,6 +218,7 @@ public: Function const&)> on_inspector_replaced_dom_node_attribute; Function const&, Optional const&)> on_inspector_requested_dom_tree_context_menu; Function on_inspector_executed_console_script; + Function on_inspector_exported_inspector_html; Function on_request_worker_agent; virtual Web::DevicePixelSize viewport_size() const = 0; diff --git a/Userland/Libraries/LibWebView/WebContentClient.cpp b/Userland/Libraries/LibWebView/WebContentClient.cpp index e80723e3f9..ed2600d593 100644 --- a/Userland/Libraries/LibWebView/WebContentClient.cpp +++ b/Userland/Libraries/LibWebView/WebContentClient.cpp @@ -693,6 +693,14 @@ void WebContentClient::inspector_did_execute_console_script(u64 page_id, String } } +void WebContentClient::inspector_did_export_inspector_html(u64 page_id, String const& html) +{ + if (auto view = view_for_page_id(page_id); view.has_value()) { + if (view->on_inspector_exported_inspector_html) + view->on_inspector_exported_inspector_html(html); + } +} + Messages::WebContentClient::RequestWorkerAgentResponse WebContentClient::request_worker_agent(u64 page_id) { 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 9de57eb0d5..bd7a40666b 100644 --- a/Userland/Libraries/LibWebView/WebContentClient.h +++ b/Userland/Libraries/LibWebView/WebContentClient.h @@ -120,6 +120,7 @@ private: virtual void inspector_did_replace_dom_node_attribute(u64 page_id, i32 node_id, size_t attribute_index, Vector const& replacement_attributes) override; virtual void inspector_did_request_dom_tree_context_menu(u64 page_id, i32 node_id, Gfx::IntPoint position, String const& type, Optional const& tag, Optional const& attribute_index) override; virtual void inspector_did_execute_console_script(u64 page_id, String const& script) override; + virtual void inspector_did_export_inspector_html(u64 page_id, String const& html) override; virtual Messages::WebContentClient::RequestWorkerAgentResponse request_worker_agent(u64 page_id) override; Optional view_for_page_id(u64, SourceLocation = SourceLocation::current()); diff --git a/Userland/Services/WebContent/PageClient.cpp b/Userland/Services/WebContent/PageClient.cpp index 1ed46b7f3c..00d40f6c8b 100644 --- a/Userland/Services/WebContent/PageClient.cpp +++ b/Userland/Services/WebContent/PageClient.cpp @@ -659,6 +659,11 @@ void PageClient::inspector_did_execute_console_script(String const& script) client().async_inspector_did_execute_console_script(m_id, script); } +void PageClient::inspector_did_export_inspector_html(String const& html) +{ + client().async_inspector_did_export_inspector_html(m_id, html); +} + ErrorOr PageClient::connect_to_webdriver(ByteString const& webdriver_ipc_path) { VERIFY(!m_webdriver); diff --git a/Userland/Services/WebContent/PageClient.h b/Userland/Services/WebContent/PageClient.h index 2220d62df8..41d91138d1 100644 --- a/Userland/Services/WebContent/PageClient.h +++ b/Userland/Services/WebContent/PageClient.h @@ -167,6 +167,7 @@ private: virtual void inspector_did_replace_dom_node_attribute(i32 node_id, size_t attribute_index, JS::NonnullGCPtr replacement_attributes) override; virtual void inspector_did_request_dom_tree_context_menu(i32 node_id, Web::CSSPixelPoint position, String const& type, Optional const& tag, Optional const& attribute_index) override; virtual void inspector_did_execute_console_script(String const& script) override; + virtual void inspector_did_export_inspector_html(String const& script) override; Web::Layout::Viewport* layout_root(); void setup_palette(); diff --git a/Userland/Services/WebContent/WebContentClient.ipc b/Userland/Services/WebContent/WebContentClient.ipc index 2a9cd05aef..45694704bb 100644 --- a/Userland/Services/WebContent/WebContentClient.ipc +++ b/Userland/Services/WebContent/WebContentClient.ipc @@ -101,5 +101,6 @@ endpoint WebContentClient inspector_did_replace_dom_node_attribute(u64 page_id, i32 node_id, size_t attribute_index, Vector replacement_attributes) =| inspector_did_request_dom_tree_context_menu(u64 page_id, i32 node_id, Gfx::IntPoint position, String type, Optional tag, Optional attribute_index) =| inspector_did_execute_console_script(u64 page_id, String script) =| + inspector_did_export_inspector_html(u64 page_id, String html) =| }