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 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7TQi-V13aCb6$*;-(=u~X z85lGs)=sqbI2<6->L0u`REtkcr0_&Q=|%;uM=L@#oLU34x4cUDbjd#B(u5^DEV$OO zve%2P=F>a3%W4N#(*ypF#giv5>Yh>&ACw{Zy7l;Pi}&*mSAS=8T6@G~b+XKblG{GV zl9Q}9Yq7;$Y*navaeT!Yon*gBLK)HV->-OR9!1 zivAMYFw6YHYhI$)cBt1UYOG$bQ#|*Yz@vyGLMj~r0gi&IH##n@o1CQR`Fx|FN7wKZ0tKaw8kcXwLvFd8!zJA%|bxc3zH)`*F zseip;rvIhYzPd#$U-Fp6ulxGmWY#NhXjFGLOAY^YS*XbJ!QY;Jn#^DBGTqI-cSq{E zMJ+>+$?7A#1qB>EPc*Cdp55KNw)pq<<%av^&7K$ax1@BxXJBApOY(MiVfYV%3-&Ib z%)r3FS>O>_%)p?h48n{ROYO@T7#P?~Jbhi+pK)?9X_<7IeF4R-WQl7;iF1B#Zfaf$ zgL6@8Vo7R>LV0FMhJw4NZ$Nk>pEv^p!$VIO#}JFtcPAP49WoGbogb*@D7w_Abw`tT zQSaKI#v0z}MIJSiecgB2GKA_V#wZptW#w;n@nc$jpzQZ<$@70c{`uLYzC*-yMy}>*T3h2f0>WTM}4&dm97e(vXTa??9H>&nMS}hCY0LT#@m^y~lHA>%~r6;&9m{ zXSeF|^=;?-zb;UjS-q~duC>PYr268yX^N*eX*OP~GINZ*?#z_^_B5Z#F@}!a{D*ey b{bReATCb-P { 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 ff371ffcecd..6f383a4e981 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 761f2c80f9f..97505b1438d 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 0058984d720..faf1a086369 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 a87479bb352..d31b2b5deca 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 55cb6d6ea2f..30c10b9c073 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 923e58af414..74ddc0e8837 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 dea99dd9e01..629f249fc77 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 e80723e3f99..ed2600d5935 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 9de57eb0d50..bd7a40666b4 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 1ed46b7f3c6..00d40f6c8bd 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 2220d62df84..41d91138d13 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 2a9cd05aeff..45694704bb3 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) =| }