mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 12:19:54 +00:00
LibDevTools+LibWebView: Inform the DevTools client about DOM mutations
This commit is contained in:
parent
2c4b420acc
commit
8bcc3d3797
Notes:
github-actions[bot]
2025-03-08 00:27:34 +00:00
Author: https://github.com/trflynn89
Commit: 8bcc3d3797
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3850
6 changed files with 150 additions and 1 deletions
|
@ -58,6 +58,7 @@ void FrameActor::handle_message(StringView type, JsonObject const&)
|
||||||
|
|
||||||
if (type == "detach"sv) {
|
if (type == "detach"sv) {
|
||||||
if (auto tab = m_tab.strong_ref()) {
|
if (auto tab = m_tab.strong_ref()) {
|
||||||
|
devtools().delegate().stop_listening_for_dom_mutations(tab->description());
|
||||||
devtools().delegate().stop_listening_for_console_messages(tab->description());
|
devtools().delegate().stop_listening_for_console_messages(tab->description());
|
||||||
tab->reset_selected_node();
|
tab->reset_selected_node();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,10 @@
|
||||||
#include <LibDevTools/Actors/LayoutInspectorActor.h>
|
#include <LibDevTools/Actors/LayoutInspectorActor.h>
|
||||||
#include <LibDevTools/Actors/TabActor.h>
|
#include <LibDevTools/Actors/TabActor.h>
|
||||||
#include <LibDevTools/Actors/WalkerActor.h>
|
#include <LibDevTools/Actors/WalkerActor.h>
|
||||||
|
#include <LibDevTools/DevToolsDelegate.h>
|
||||||
#include <LibDevTools/DevToolsServer.h>
|
#include <LibDevTools/DevToolsServer.h>
|
||||||
#include <LibWeb/DOM/NodeType.h>
|
#include <LibWeb/DOM/NodeType.h>
|
||||||
|
#include <LibWebView/Mutation.h>
|
||||||
|
|
||||||
namespace DevTools {
|
namespace DevTools {
|
||||||
|
|
||||||
|
@ -25,9 +27,21 @@ WalkerActor::WalkerActor(DevToolsServer& devtools, String name, WeakPtr<TabActor
|
||||||
, m_dom_tree(move(dom_tree))
|
, m_dom_tree(move(dom_tree))
|
||||||
{
|
{
|
||||||
populate_dom_tree_cache();
|
populate_dom_tree_cache();
|
||||||
|
|
||||||
|
if (auto tab = m_tab.strong_ref()) {
|
||||||
|
devtools.delegate().listen_for_dom_mutations(tab->description(),
|
||||||
|
[weak_self = make_weak_ptr<WalkerActor>()](WebView::Mutation mutation) {
|
||||||
|
if (auto self = weak_self.strong_ref())
|
||||||
|
self->new_dom_node_mutation(move(mutation));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WalkerActor::~WalkerActor() = default;
|
WalkerActor::~WalkerActor()
|
||||||
|
{
|
||||||
|
if (auto tab = m_tab.strong_ref())
|
||||||
|
devtools().delegate().stop_listening_for_dom_mutations(tab->description());
|
||||||
|
}
|
||||||
|
|
||||||
void WalkerActor::handle_message(StringView type, JsonObject const& message)
|
void WalkerActor::handle_message(StringView type, JsonObject const& message)
|
||||||
{
|
{
|
||||||
|
@ -71,6 +85,14 @@ void WalkerActor::handle_message(StringView type, JsonObject const& message)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type == "getMutations"sv) {
|
||||||
|
response.set("mutations"sv, serialize_mutations());
|
||||||
|
send_message(move(response));
|
||||||
|
|
||||||
|
m_has_new_mutations_since_last_mutations_request = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (type == "getOffsetParent"sv) {
|
if (type == "getOffsetParent"sv) {
|
||||||
response.set("node"sv, JsonValue {});
|
response.set("node"sv, JsonValue {});
|
||||||
send_message(move(response));
|
send_message(move(response));
|
||||||
|
@ -295,6 +317,97 @@ Optional<JsonObject const&> WalkerActor::find_node_by_selector(JsonObject const&
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WalkerActor::new_dom_node_mutation(WebView::Mutation mutation)
|
||||||
|
{
|
||||||
|
auto serialized_target = JsonValue::from_string(mutation.serialized_target);
|
||||||
|
if (serialized_target.is_error() || !serialized_target.value().is_object()) {
|
||||||
|
dbgln_if(DEVTOOLS_DEBUG, "Unable to parse serialized target as JSON object: {}", serialized_target.error());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!replace_node_in_tree(move(serialized_target.release_value().as_object()))) {
|
||||||
|
dbgln_if(DEVTOOLS_DEBUG, "Unable to apply mutation to DOM tree");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_dom_node_mutations.append(move(mutation));
|
||||||
|
|
||||||
|
if (m_has_new_mutations_since_last_mutations_request)
|
||||||
|
return;
|
||||||
|
|
||||||
|
JsonObject message;
|
||||||
|
message.set("from"sv, name());
|
||||||
|
message.set("type"sv, "newMutations"sv);
|
||||||
|
send_message(move(message));
|
||||||
|
|
||||||
|
m_has_new_mutations_since_last_mutations_request = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonValue WalkerActor::serialize_mutations()
|
||||||
|
{
|
||||||
|
JsonArray mutations;
|
||||||
|
mutations.ensure_capacity(m_dom_node_mutations.size());
|
||||||
|
|
||||||
|
for (auto& mutation : m_dom_node_mutations) {
|
||||||
|
auto target = m_dom_node_id_to_actor_map.get(mutation.target);
|
||||||
|
if (!target.has_value())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
JsonObject serialized;
|
||||||
|
serialized.set("target"sv, target.release_value());
|
||||||
|
serialized.set("type"sv, move(mutation.type));
|
||||||
|
|
||||||
|
mutation.mutation.visit(
|
||||||
|
[&](WebView::AttributeMutation& mutation) {
|
||||||
|
serialized.set("attributeName"sv, move(mutation.attribute_name));
|
||||||
|
|
||||||
|
if (mutation.new_value.has_value())
|
||||||
|
serialized.set("newValue"sv, mutation.new_value.release_value());
|
||||||
|
else
|
||||||
|
serialized.set("newValue"sv, JsonValue {});
|
||||||
|
},
|
||||||
|
[&](WebView::CharacterDataMutation& mutation) {
|
||||||
|
serialized.set("newValue"sv, move(mutation.new_value));
|
||||||
|
},
|
||||||
|
[&](WebView::ChildListMutation const& mutation) {
|
||||||
|
JsonArray added;
|
||||||
|
JsonArray removed;
|
||||||
|
|
||||||
|
for (auto id : mutation.added) {
|
||||||
|
if (auto node = m_dom_node_id_to_actor_map.get(id); node.has_value())
|
||||||
|
added.must_append(node.release_value());
|
||||||
|
}
|
||||||
|
for (auto id : mutation.removed) {
|
||||||
|
if (auto node = m_dom_node_id_to_actor_map.get(id); node.has_value())
|
||||||
|
removed.must_append(node.release_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized.set("added"sv, move(added));
|
||||||
|
serialized.set("removed"sv, move(removed));
|
||||||
|
serialized.set("numChildren"sv, mutation.target_child_count);
|
||||||
|
});
|
||||||
|
|
||||||
|
mutations.must_append(move(serialized));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_dom_node_mutations.clear();
|
||||||
|
return mutations;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WalkerActor::replace_node_in_tree(JsonObject replacement)
|
||||||
|
{
|
||||||
|
auto const& actor = actor_for_node(replacement);
|
||||||
|
|
||||||
|
auto node = m_actor_to_dom_node_map.get(actor.name());
|
||||||
|
if (!node.has_value() || !node.value())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const_cast<JsonObject&>(*node.value()) = move(replacement);
|
||||||
|
populate_dom_tree_cache();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void WalkerActor::populate_dom_tree_cache()
|
void WalkerActor::populate_dom_tree_cache()
|
||||||
{
|
{
|
||||||
m_dom_node_to_parent_map.clear();
|
m_dom_node_to_parent_map.clear();
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <LibDevTools/Actor.h>
|
#include <LibDevTools/Actor.h>
|
||||||
#include <LibDevTools/Actors/NodeActor.h>
|
#include <LibDevTools/Actors/NodeActor.h>
|
||||||
#include <LibWeb/Forward.h>
|
#include <LibWeb/Forward.h>
|
||||||
|
#include <LibWebView/Forward.h>
|
||||||
|
|
||||||
namespace DevTools {
|
namespace DevTools {
|
||||||
|
|
||||||
|
@ -42,6 +43,11 @@ private:
|
||||||
JsonValue serialize_node(JsonObject const&) const;
|
JsonValue serialize_node(JsonObject const&) const;
|
||||||
Optional<JsonObject const&> find_node_by_selector(JsonObject const& node, StringView selector);
|
Optional<JsonObject const&> find_node_by_selector(JsonObject const& node, StringView selector);
|
||||||
|
|
||||||
|
void new_dom_node_mutation(WebView::Mutation);
|
||||||
|
JsonValue serialize_mutations();
|
||||||
|
|
||||||
|
bool replace_node_in_tree(JsonObject replacement);
|
||||||
|
|
||||||
void populate_dom_tree_cache();
|
void populate_dom_tree_cache();
|
||||||
void populate_dom_tree_cache(JsonObject& node, JsonObject const* parent);
|
void populate_dom_tree_cache(JsonObject& node, JsonObject const* parent);
|
||||||
|
|
||||||
|
@ -52,6 +58,9 @@ private:
|
||||||
|
|
||||||
JsonObject m_dom_tree;
|
JsonObject m_dom_tree;
|
||||||
|
|
||||||
|
Vector<WebView::Mutation> m_dom_node_mutations;
|
||||||
|
bool m_has_new_mutations_since_last_mutations_request { false };
|
||||||
|
|
||||||
HashMap<JsonObject const*, JsonObject const*> m_dom_node_to_parent_map;
|
HashMap<JsonObject const*, JsonObject const*> m_dom_node_to_parent_map;
|
||||||
HashMap<String, JsonObject const*> m_actor_to_dom_node_map;
|
HashMap<String, JsonObject const*> m_actor_to_dom_node_map;
|
||||||
HashMap<Web::UniqueNodeID, String> m_dom_node_id_to_actor_map;
|
HashMap<Web::UniqueNodeID, String> m_dom_node_id_to_actor_map;
|
||||||
|
|
|
@ -37,6 +37,10 @@ public:
|
||||||
virtual void highlight_dom_node(TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const { }
|
virtual void highlight_dom_node(TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const { }
|
||||||
virtual void clear_highlighted_dom_node(TabDescription const&) const { }
|
virtual void clear_highlighted_dom_node(TabDescription const&) const { }
|
||||||
|
|
||||||
|
using OnDOMMutationReceived = Function<void(WebView::Mutation)>;
|
||||||
|
virtual void listen_for_dom_mutations(TabDescription const&, OnDOMMutationReceived) const { }
|
||||||
|
virtual void stop_listening_for_dom_mutations(TabDescription const&) const { }
|
||||||
|
|
||||||
using OnScriptEvaluationComplete = Function<void(ErrorOr<JsonValue>)>;
|
using OnScriptEvaluationComplete = Function<void(ErrorOr<JsonValue>)>;
|
||||||
virtual void evaluate_javascript(TabDescription const&, String, OnScriptEvaluationComplete) const { }
|
virtual void evaluate_javascript(TabDescription const&, String, OnScriptEvaluationComplete) const { }
|
||||||
|
|
||||||
|
|
|
@ -417,6 +417,26 @@ void Application::clear_highlighted_dom_node(DevTools::TabDescription const& des
|
||||||
view->clear_highlighted_dom_node();
|
view->clear_highlighted_dom_node();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::listen_for_dom_mutations(DevTools::TabDescription const& description, OnDOMMutationReceived on_dom_mutation_received) const
|
||||||
|
{
|
||||||
|
auto view = ViewImplementation::find_view_by_id(description.id);
|
||||||
|
if (!view.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
view->on_dom_mutation_received = move(on_dom_mutation_received);
|
||||||
|
view->set_listen_for_dom_mutations(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::stop_listening_for_dom_mutations(DevTools::TabDescription const& description) const
|
||||||
|
{
|
||||||
|
auto view = ViewImplementation::find_view_by_id(description.id);
|
||||||
|
if (!view.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
view->on_dom_mutation_received = nullptr;
|
||||||
|
view->set_listen_for_dom_mutations(false);
|
||||||
|
}
|
||||||
|
|
||||||
void Application::evaluate_javascript(DevTools::TabDescription const& description, String script, OnScriptEvaluationComplete on_complete) const
|
void Application::evaluate_javascript(DevTools::TabDescription const& description, String script, OnScriptEvaluationComplete on_complete) const
|
||||||
{
|
{
|
||||||
auto view = ViewImplementation::find_view_by_id(description.id);
|
auto view = ViewImplementation::find_view_by_id(description.id);
|
||||||
|
|
|
@ -96,6 +96,8 @@ private:
|
||||||
virtual void clear_inspected_dom_node(DevTools::TabDescription const&) const override;
|
virtual void clear_inspected_dom_node(DevTools::TabDescription const&) const override;
|
||||||
virtual void highlight_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const override;
|
virtual void highlight_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const override;
|
||||||
virtual void clear_highlighted_dom_node(DevTools::TabDescription const&) const override;
|
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 evaluate_javascript(DevTools::TabDescription const&, String, OnScriptEvaluationComplete) const override;
|
virtual void evaluate_javascript(DevTools::TabDescription const&, String, OnScriptEvaluationComplete) const override;
|
||||||
virtual void listen_for_console_messages(DevTools::TabDescription const&, OnConsoleMessageAvailable, OnReceivedConsoleMessages) const override;
|
virtual void listen_for_console_messages(DevTools::TabDescription const&, OnConsoleMessageAvailable, OnReceivedConsoleMessages) const override;
|
||||||
virtual void stop_listening_for_console_messages(DevTools::TabDescription const&) const override;
|
virtual void stop_listening_for_console_messages(DevTools::TabDescription const&) const override;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue