From d75eadc3c4552b56a7315da2ea78252fad112b1d Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 10 Mar 2025 17:36:41 -0400 Subject: [PATCH] LibDevTools+LibWebView+WebContent: Implement editing DOM node HTML These commands are used for the "Edit As HTML" feature in DevTools. This renames our existing HTML getter IPC to indicate that it is for outer HTML. DevTools will need a separate inner HTML getter. --- Libraries/LibDevTools/Actors/WalkerActor.cpp | 65 ++++++++++++++++++++ Libraries/LibDevTools/DevToolsDelegate.h | 3 + Libraries/LibWebView/Application.cpp | 23 +++++++ Libraries/LibWebView/Application.h | 2 + Libraries/LibWebView/InspectorClient.cpp | 2 +- Libraries/LibWebView/ViewImplementation.cpp | 15 +++-- Libraries/LibWebView/ViewImplementation.h | 5 +- Libraries/LibWebView/WebContentClient.cpp | 2 +- Services/WebContent/ConnectionFromClient.cpp | 64 ++++++++++++------- Services/WebContent/ConnectionFromClient.h | 3 +- Services/WebContent/WebContentServer.ipc | 3 +- 11 files changed, 155 insertions(+), 32 deletions(-) diff --git a/Libraries/LibDevTools/Actors/WalkerActor.cpp b/Libraries/LibDevTools/Actors/WalkerActor.cpp index 0109efa04fb..6b6789cde0f 100644 --- a/Libraries/LibDevTools/Actors/WalkerActor.cpp +++ b/Libraries/LibDevTools/Actors/WalkerActor.cpp @@ -214,6 +214,36 @@ void WalkerActor::handle_message(StringView type, JsonObject const& message) return; } + if (type == "outerHTML"sv) { + auto node = message.get_string("node"sv); + if (!node.has_value()) { + send_missing_parameter_error("node"sv); + return; + } + + if (auto dom_node = WalkerActor::dom_node_for(*this, *node); dom_node.has_value()) { + auto block_token = block_responses(); + + devtools().delegate().get_dom_node_outer_html( + dom_node->tab->description(), dom_node->identifier.id, + [weak_self = make_weak_ptr(), block_token = move(block_token)](ErrorOr html) mutable { + if (html.is_error()) { + dbgln_if(DEVTOOLS_DEBUG, "Unable to edit DOM node: {}", html.error()); + return; + } + + if (auto self = weak_self.strong_ref()) { + JsonObject message; + message.set("from"sv, self->name()); + message.set("value"sv, html.release_value()); + self->send_message(move(message), move(block_token)); + } + }); + } + + return; + } + if (type == "previousSibling"sv) { auto node = message.get_string("node"sv); if (!node.has_value()) { @@ -307,6 +337,41 @@ void WalkerActor::handle_message(StringView type, JsonObject const& message) return; } + if (type == "setOuterHTML"sv) { + auto node = message.get_string("node"sv); + if (!node.has_value()) { + send_missing_parameter_error("node"sv); + return; + } + + auto value = message.get_string("value"sv); + if (!value.has_value()) { + send_missing_parameter_error("value"sv); + return; + } + + if (auto dom_node = WalkerActor::dom_node_for(*this, *node); dom_node.has_value()) { + auto block_token = block_responses(); + + devtools().delegate().set_dom_node_outer_html( + dom_node->tab->description(), dom_node->identifier.id, value.release_value(), + [weak_self = make_weak_ptr(), block_token = move(block_token)](ErrorOr node_id) mutable { + if (node_id.is_error()) { + dbgln_if(DEVTOOLS_DEBUG, "Unable to edit DOM node: {}", node_id.error()); + return; + } + + if (auto self = weak_self.strong_ref()) { + JsonObject message; + message.set("from"sv, self->name()); + self->send_message(move(message), move(block_token)); + } + }); + } + + return; + } + if (type == "watchRootNode"sv) { response.set("type"sv, "root-available"sv); response.set("node"sv, serialize_root()); diff --git a/Libraries/LibDevTools/DevToolsDelegate.h b/Libraries/LibDevTools/DevToolsDelegate.h index c24152c74ee..418602dd6cf 100644 --- a/Libraries/LibDevTools/DevToolsDelegate.h +++ b/Libraries/LibDevTools/DevToolsDelegate.h @@ -41,7 +41,10 @@ public: virtual void listen_for_dom_mutations(TabDescription const&, OnDOMMutationReceived) const { } virtual void stop_listening_for_dom_mutations(TabDescription const&) const { } + using OnDOMNodeHTMLReceived = Function)>; using OnDOMNodeEditComplete = Function)>; + virtual void get_dom_node_outer_html(TabDescription const&, Web::UniqueNodeID, OnDOMNodeHTMLReceived) const { } + virtual void set_dom_node_outer_html(TabDescription const&, Web::UniqueNodeID, String const&, OnDOMNodeEditComplete) const { } virtual void set_dom_node_text(TabDescription const&, Web::UniqueNodeID, String const&, OnDOMNodeEditComplete) const { } virtual void set_dom_node_tag(TabDescription const&, Web::UniqueNodeID, String const&, OnDOMNodeEditComplete) const { } virtual void add_dom_node_attributes(TabDescription const&, Web::UniqueNodeID, ReadonlySpan, OnDOMNodeEditComplete) const { } diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index adf1ffdd628..7df0bd98d77 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -504,6 +504,29 @@ static void edit_dom_node(DevTools::TabDescription const& description, Applicati edit(*view); } +void Application::get_dom_node_outer_html(DevTools::TabDescription const& description, Web::UniqueNodeID node_id, OnDOMNodeHTMLReceived on_complete) const +{ + auto view = ViewImplementation::find_view_by_id(description.id); + if (!view.has_value()) { + on_complete(Error::from_string_literal("Unable to locate tab")); + return; + } + + view->on_received_dom_node_html = [&view = *view, on_complete = move(on_complete)](auto html) { + view.on_received_dom_node_html = nullptr; + on_complete(html); + }; + + view->get_dom_node_outer_html(node_id); +} + +void Application::set_dom_node_outer_html(DevTools::TabDescription const& description, Web::UniqueNodeID node_id, String const& value, OnDOMNodeEditComplete on_complete) const +{ + edit_dom_node(description, move(on_complete), [&](auto& view) { + view.set_dom_node_outer_html(node_id, value); + }); +} + void Application::set_dom_node_text(DevTools::TabDescription const& description, Web::UniqueNodeID node_id, String const& value, OnDOMNodeEditComplete on_complete) const { edit_dom_node(description, move(on_complete), [&](auto& view) { diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index 1595f8a7d42..6c67bce0cd6 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -100,6 +100,8 @@ private: virtual void clear_highlighted_dom_node(DevTools::TabDescription const&) const override; virtual void listen_for_dom_mutations(DevTools::TabDescription const&, OnDOMMutationReceived) const override; virtual void stop_listening_for_dom_mutations(DevTools::TabDescription const&) const override; + virtual void get_dom_node_outer_html(DevTools::TabDescription const&, Web::UniqueNodeID, OnDOMNodeHTMLReceived) const override; + virtual void set_dom_node_outer_html(DevTools::TabDescription const&, Web::UniqueNodeID, String const&, OnDOMNodeEditComplete) const override; virtual void set_dom_node_text(DevTools::TabDescription const&, Web::UniqueNodeID, String const&, OnDOMNodeEditComplete) const override; virtual void set_dom_node_tag(DevTools::TabDescription const&, Web::UniqueNodeID, String const&, OnDOMNodeEditComplete) const override; virtual void add_dom_node_attributes(DevTools::TabDescription const&, Web::UniqueNodeID, ReadonlySpan, OnDOMNodeEditComplete) const override; diff --git a/Libraries/LibWebView/InspectorClient.cpp b/Libraries/LibWebView/InspectorClient.cpp index 1eb52698ee7..c27f02c8ee6 100644 --- a/Libraries/LibWebView/InspectorClient.cpp +++ b/Libraries/LibWebView/InspectorClient.cpp @@ -368,7 +368,7 @@ void InspectorClient::context_menu_copy_dom_node() { VERIFY(m_context_menu_data.has_value()); - m_content_web_view.get_dom_node_html(m_context_menu_data->dom_node_id); + m_content_web_view.get_dom_node_outer_html(m_context_menu_data->dom_node_id); m_context_menu_data.clear(); } diff --git a/Libraries/LibWebView/ViewImplementation.cpp b/Libraries/LibWebView/ViewImplementation.cpp index 3ae345ebe32..aacaea31d10 100644 --- a/Libraries/LibWebView/ViewImplementation.cpp +++ b/Libraries/LibWebView/ViewImplementation.cpp @@ -354,6 +354,16 @@ void ViewImplementation::set_listen_for_dom_mutations(bool listen_for_dom_mutati client().async_set_listen_for_dom_mutations(page_id(), listen_for_dom_mutations); } +void ViewImplementation::get_dom_node_outer_html(Web::UniqueNodeID node_id) +{ + client().async_get_dom_node_outer_html(page_id(), node_id); +} + +void ViewImplementation::set_dom_node_outer_html(Web::UniqueNodeID node_id, String const& html) +{ + client().async_set_dom_node_outer_html(page_id(), node_id, html); +} + void ViewImplementation::set_dom_node_text(Web::UniqueNodeID node_id, String const& text) { client().async_set_dom_node_text(page_id(), node_id, text); @@ -394,11 +404,6 @@ void ViewImplementation::remove_dom_node(Web::UniqueNodeID node_id) client().async_remove_dom_node(page_id(), node_id); } -void ViewImplementation::get_dom_node_html(Web::UniqueNodeID node_id) -{ - client().async_get_dom_node_html(page_id(), node_id); -} - void ViewImplementation::list_style_sheets() { client().async_list_style_sheets(page_id()); diff --git a/Libraries/LibWebView/ViewImplementation.h b/Libraries/LibWebView/ViewImplementation.h index c1f8ed73592..eb6e322dafa 100644 --- a/Libraries/LibWebView/ViewImplementation.h +++ b/Libraries/LibWebView/ViewImplementation.h @@ -115,6 +115,8 @@ public: void clear_highlighted_dom_node(); void set_listen_for_dom_mutations(bool); + void get_dom_node_outer_html(Web::UniqueNodeID node_id); + void set_dom_node_outer_html(Web::UniqueNodeID node_id, String const& html); void set_dom_node_text(Web::UniqueNodeID node_id, String const& text); void set_dom_node_tag(Web::UniqueNodeID node_id, String const& name); void add_dom_node_attributes(Web::UniqueNodeID node_id, ReadonlySpan attributes); @@ -123,7 +125,6 @@ public: void create_child_text_node(Web::UniqueNodeID node_id); void clone_dom_node(Web::UniqueNodeID node_id); void remove_dom_node(Web::UniqueNodeID node_id); - void get_dom_node_html(Web::UniqueNodeID node_id); void list_style_sheets(); void request_style_sheet_source(Web::CSS::StyleSheetIdentifier const&); @@ -218,7 +219,7 @@ public: Function on_received_hovered_node_id; Function on_dom_mutation_received; Function const& node_id)> on_finshed_editing_dom_node; - Function on_received_dom_node_html; + Function on_received_dom_node_html; Function on_received_js_console_result; Function on_console_message_available; Function const& message_types, Vector const& messages)> on_received_styled_console_messages; diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp index be244be42ee..146140b2f06 100644 --- a/Libraries/LibWebView/WebContentClient.cpp +++ b/Libraries/LibWebView/WebContentClient.cpp @@ -371,7 +371,7 @@ void WebContentClient::did_get_dom_node_html(u64 page_id, String html) { if (auto view = view_for_page_id(page_id); view.has_value()) { if (view->on_received_dom_node_html) - view->on_received_dom_node_html(html); + view->on_received_dom_node_html(move(html)); } } diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index d68f468c879..a1ac9502f24 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -676,6 +676,49 @@ void ConnectionFromClient::set_listen_for_dom_mutations(u64 page_id, bool listen page->page().set_listen_for_dom_mutations(listen_for_dom_mutations); } +void ConnectionFromClient::get_dom_node_outer_html(u64 page_id, Web::UniqueNodeID node_id) +{ + auto* dom_node = Web::DOM::Node::from_unique_id(node_id); + if (!dom_node) + return; + + String html; + + if (dom_node->is_element()) { + auto const& element = static_cast(*dom_node); + html = element.outer_html().release_value_but_fixme_should_propagate_errors(); + } else if (dom_node->is_text() || dom_node->is_comment()) { + auto const& character_data = static_cast(*dom_node); + html = character_data.data(); + } else { + return; + } + + async_did_get_dom_node_html(page_id, html); +} + +void ConnectionFromClient::set_dom_node_outer_html(u64 page_id, Web::UniqueNodeID node_id, String html) +{ + auto* dom_node = Web::DOM::Node::from_unique_id(node_id); + if (!dom_node) { + async_did_finish_editing_dom_node(page_id, {}); + return; + } + + if (dom_node->is_element()) { + auto& element = static_cast(*dom_node); + element.set_outer_html(html).release_value_but_fixme_should_propagate_errors(); + } else if (dom_node->is_text() || dom_node->is_comment()) { + auto& character_data = static_cast(*dom_node); + character_data.set_data(html); + } else { + async_did_finish_editing_dom_node(page_id, {}); + return; + } + + async_did_finish_editing_dom_node(page_id, node_id); +} + void ConnectionFromClient::set_dom_node_text(u64 page_id, Web::UniqueNodeID node_id, String text) { auto* dom_node = Web::DOM::Node::from_unique_id(node_id); @@ -826,27 +869,6 @@ void ConnectionFromClient::remove_dom_node(u64 page_id, Web::UniqueNodeID node_i async_did_finish_editing_dom_node(page_id, previous_dom_node->unique_id()); } -void ConnectionFromClient::get_dom_node_html(u64 page_id, Web::UniqueNodeID node_id) -{ - auto* dom_node = Web::DOM::Node::from_unique_id(node_id); - if (!dom_node) - return; - - String html; - - if (dom_node->is_element()) { - auto const& element = static_cast(*dom_node); - html = element.outer_html().release_value_but_fixme_should_propagate_errors(); - } else if (dom_node->is_text() || dom_node->is_comment()) { - auto const& character_data = static_cast(*dom_node); - html = character_data.data(); - } else { - return; - } - - async_did_get_dom_node_html(page_id, html); -} - void ConnectionFromClient::take_document_screenshot(u64 page_id) { auto page = this->page(page_id); diff --git a/Services/WebContent/ConnectionFromClient.h b/Services/WebContent/ConnectionFromClient.h index 8ba5b0eb351..65d7146ed92 100644 --- a/Services/WebContent/ConnectionFromClient.h +++ b/Services/WebContent/ConnectionFromClient.h @@ -84,6 +84,8 @@ private: virtual void request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier) override; virtual void set_listen_for_dom_mutations(u64 page_id, bool) override; + virtual void get_dom_node_outer_html(u64 page_id, Web::UniqueNodeID node_id) override; + virtual void set_dom_node_outer_html(u64 page_id, Web::UniqueNodeID node_id, String html) override; virtual void set_dom_node_text(u64 page_id, Web::UniqueNodeID node_id, String text) override; virtual void set_dom_node_tag(u64 page_id, Web::UniqueNodeID node_id, String name) override; virtual void add_dom_node_attributes(u64 page_id, Web::UniqueNodeID node_id, Vector attributes) override; @@ -92,7 +94,6 @@ private: virtual void create_child_text_node(u64 page_id, Web::UniqueNodeID node_id) override; virtual void clone_dom_node(u64 page_id, Web::UniqueNodeID node_id) override; virtual void remove_dom_node(u64 page_id, Web::UniqueNodeID node_id) override; - virtual void get_dom_node_html(u64 page_id, Web::UniqueNodeID node_id) override; virtual void set_content_filters(u64 page_id, Vector) override; virtual void set_autoplay_allowed_on_all_websites(u64 page_id) override; diff --git a/Services/WebContent/WebContentServer.ipc b/Services/WebContent/WebContentServer.ipc index 18b0b036fcc..ddad965753a 100644 --- a/Services/WebContent/WebContentServer.ipc +++ b/Services/WebContent/WebContentServer.ipc @@ -57,6 +57,8 @@ endpoint WebContentServer request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier) =| set_listen_for_dom_mutations(u64 page_id, bool listen_for_dom_mutations) =| + get_dom_node_outer_html(u64 page_id, Web::UniqueNodeID node_id) =| + set_dom_node_outer_html(u64 page_id, Web::UniqueNodeID node_id, String html) =| set_dom_node_text(u64 page_id, Web::UniqueNodeID node_id, String text) =| set_dom_node_tag(u64 page_id, Web::UniqueNodeID node_id, String name) =| add_dom_node_attributes(u64 page_id, Web::UniqueNodeID node_id, Vector attributes) =| @@ -65,7 +67,6 @@ endpoint WebContentServer create_child_text_node(u64 page_id, Web::UniqueNodeID node_id) =| clone_dom_node(u64 page_id, Web::UniqueNodeID node_id) =| remove_dom_node(u64 page_id, Web::UniqueNodeID node_id) =| - get_dom_node_html(u64 page_id, Web::UniqueNodeID node_id) =| take_document_screenshot(u64 page_id) =| take_dom_node_screenshot(u64 page_id, Web::UniqueNodeID node_id) =|