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.
This commit is contained in:
Timothy Flynn 2025-03-10 17:36:41 -04:00 committed by Tim Flynn
parent aca4385daf
commit d75eadc3c4
Notes: github-actions[bot] 2025-03-11 13:52:05 +00:00
11 changed files with 155 additions and 32 deletions

View file

@ -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<WalkerActor>(), block_token = move(block_token)](ErrorOr<String> 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<WalkerActor>(), block_token = move(block_token)](ErrorOr<Web::UniqueNodeID> 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());

View file

@ -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<void(ErrorOr<String>)>;
using OnDOMNodeEditComplete = Function<void(ErrorOr<Web::UniqueNodeID>)>;
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<WebView::Attribute>, OnDOMNodeEditComplete) const { }

View file

@ -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) {

View file

@ -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<Attribute>, OnDOMNodeEditComplete) const override;

View file

@ -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();
}

View file

@ -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());

View file

@ -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<Attribute> 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<void(Web::UniqueNodeID)> on_received_hovered_node_id;
Function<void(Mutation)> on_dom_mutation_received;
Function<void(Optional<Web::UniqueNodeID> const& node_id)> on_finshed_editing_dom_node;
Function<void(String const&)> on_received_dom_node_html;
Function<void(String)> on_received_dom_node_html;
Function<void(JsonValue)> on_received_js_console_result;
Function<void(i32 message_id)> on_console_message_available;
Function<void(i32 start_index, Vector<String> const& message_types, Vector<String> const& messages)> on_received_styled_console_messages;

View file

@ -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));
}
}

View file

@ -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<Web::DOM::Element const&>(*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<Web::DOM::CharacterData const&>(*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<Web::DOM::Element&>(*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<Web::DOM::CharacterData&>(*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<Web::DOM::Element const&>(*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<Web::DOM::CharacterData const&>(*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);

View file

@ -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<WebView::Attribute> 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<String>) override;
virtual void set_autoplay_allowed_on_all_websites(u64 page_id) override;

View file

@ -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<WebView::Attribute> 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) =|