2025-02-15 07:57:36 -05:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <AK/JsonArray.h>
|
2025-02-19 09:28:02 -05:00
|
|
|
#include <AK/StringUtils.h>
|
2025-02-21 16:17:20 -05:00
|
|
|
#include <LibDevTools/Actors/LayoutInspectorActor.h>
|
2025-02-15 07:57:36 -05:00
|
|
|
#include <LibDevTools/Actors/TabActor.h>
|
|
|
|
#include <LibDevTools/Actors/WalkerActor.h>
|
2025-03-06 19:56:03 -05:00
|
|
|
#include <LibDevTools/DevToolsDelegate.h>
|
2025-02-21 15:19:35 -05:00
|
|
|
#include <LibDevTools/DevToolsServer.h>
|
2025-02-15 07:57:36 -05:00
|
|
|
#include <LibWeb/DOM/NodeType.h>
|
2025-03-06 19:56:03 -05:00
|
|
|
#include <LibWebView/Mutation.h>
|
2025-02-15 07:57:36 -05:00
|
|
|
|
|
|
|
namespace DevTools {
|
|
|
|
|
2025-02-19 09:28:02 -05:00
|
|
|
NonnullRefPtr<WalkerActor> WalkerActor::create(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, JsonObject dom_tree)
|
2025-02-15 07:57:36 -05:00
|
|
|
{
|
|
|
|
return adopt_ref(*new WalkerActor(devtools, move(name), move(tab), move(dom_tree)));
|
|
|
|
}
|
|
|
|
|
2025-02-19 09:28:02 -05:00
|
|
|
WalkerActor::WalkerActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, JsonObject dom_tree)
|
2025-02-15 07:57:36 -05:00
|
|
|
: Actor(devtools, move(name))
|
|
|
|
, m_tab(move(tab))
|
|
|
|
, m_dom_tree(move(dom_tree))
|
|
|
|
{
|
2025-03-06 19:49:27 -05:00
|
|
|
populate_dom_tree_cache();
|
2025-03-06 19:56:03 -05:00
|
|
|
|
|
|
|
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));
|
|
|
|
});
|
|
|
|
}
|
2025-02-15 07:57:36 -05:00
|
|
|
}
|
|
|
|
|
2025-03-06 19:56:03 -05:00
|
|
|
WalkerActor::~WalkerActor()
|
|
|
|
{
|
|
|
|
if (auto tab = m_tab.strong_ref())
|
|
|
|
devtools().delegate().stop_listening_for_dom_mutations(tab->description());
|
|
|
|
}
|
2025-02-15 07:57:36 -05:00
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
void WalkerActor::handle_message(Message const& message)
|
2025-02-15 07:57:36 -05:00
|
|
|
{
|
|
|
|
JsonObject response;
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "children"sv) {
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-02-15 07:57:36 -05:00
|
|
|
return;
|
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
auto ancestor_node = WalkerActor::dom_node_for(*this, *node);
|
|
|
|
if (!ancestor_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
|
|
|
}
|
2025-02-15 07:57:36 -05:00
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
JsonArray nodes;
|
2025-02-15 07:57:36 -05:00
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
if (auto children = ancestor_node->node.get_array("children"sv); children.has_value()) {
|
|
|
|
children->for_each([&](JsonValue const& child) {
|
|
|
|
nodes.must_append(serialize_node(child.as_object()));
|
|
|
|
});
|
2025-02-15 07:57:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
response.set("hasFirst"sv, !nodes.is_empty());
|
|
|
|
response.set("hasLast"sv, !nodes.is_empty());
|
|
|
|
response.set("nodes"sv, move(nodes));
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_response(message, move(response));
|
2025-02-15 07:57:36 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "duplicateNode"sv) {
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-03-07 09:44:24 -05:00
|
|
|
return;
|
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
auto dom_node = WalkerActor::dom_node_for(*this, *node);
|
|
|
|
if (!dom_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
|
|
|
}
|
2025-03-07 09:44:24 -05:00
|
|
|
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
devtools().delegate().clone_dom_node(dom_node->tab->description(), dom_node->identifier.id, default_async_handler(message));
|
2025-03-11 10:41:47 -04:00
|
|
|
return;
|
2025-03-07 09:44:24 -05:00
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "editTagName"sv) {
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-03-07 09:26:36 -05:00
|
|
|
return;
|
|
|
|
|
2025-03-11 09:05:01 -04:00
|
|
|
auto tag_name = get_required_parameter<String>(message, "tagName"sv);
|
|
|
|
if (!tag_name.has_value())
|
2025-03-07 09:26:36 -05:00
|
|
|
return;
|
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
auto dom_node = WalkerActor::dom_node_for(*this, *node);
|
|
|
|
if (!dom_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
devtools().delegate().set_dom_node_tag(dom_node->tab->description(), dom_node->identifier.id, *tag_name, default_async_handler(message));
|
2025-03-07 09:26:36 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "getLayoutInspector"sv) {
|
2025-02-21 16:17:20 -05:00
|
|
|
if (!m_layout_inspector)
|
|
|
|
m_layout_inspector = devtools().register_actor<LayoutInspectorActor>();
|
|
|
|
|
|
|
|
JsonObject actor;
|
|
|
|
actor.set("actor"sv, m_layout_inspector->name());
|
|
|
|
|
|
|
|
response.set("actor"sv, move(actor));
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_response(message, move(response));
|
2025-02-21 16:17:20 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "getMutations"sv) {
|
2025-03-06 19:56:03 -05:00
|
|
|
response.set("mutations"sv, serialize_mutations());
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_response(message, move(response));
|
2025-03-06 19:56:03 -05:00
|
|
|
|
|
|
|
m_has_new_mutations_since_last_mutations_request = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "getOffsetParent"sv) {
|
2025-02-21 16:20:53 -05:00
|
|
|
response.set("node"sv, JsonValue {});
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_response(message, move(response));
|
2025-02-21 16:20:53 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "innerHTML"sv) {
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-03-10 18:15:31 -04:00
|
|
|
return;
|
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
auto dom_node = WalkerActor::dom_node_for(*this, *node);
|
|
|
|
if (!dom_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
|
|
|
}
|
2025-03-10 18:15:31 -04:00
|
|
|
|
2025-03-11 09:47:41 -04:00
|
|
|
devtools().delegate().get_dom_node_inner_html(dom_node->tab->description(), dom_node->identifier.id,
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
async_handler(message, [](auto&, auto html, auto& response) {
|
2025-03-11 09:47:41 -04:00
|
|
|
response.set("value"sv, move(html));
|
|
|
|
}));
|
2025-03-10 18:15:31 -04:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "insertAdjacentHTML") {
|
2025-03-07 09:19:28 -05:00
|
|
|
// FIXME: This message also contains `value` and `position` parameters, containing the HTML to insert and the
|
|
|
|
// location to insert it. For the "Create New Node" action, this is always "<div></div>" and "beforeEnd",
|
|
|
|
// which is exactly what our WebView implementation currently supports.
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-03-07 09:19:28 -05:00
|
|
|
return;
|
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
auto dom_node = WalkerActor::dom_node_for(*this, *node);
|
|
|
|
if (!dom_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
|
|
|
}
|
2025-03-07 09:19:28 -05:00
|
|
|
|
2025-03-11 09:47:41 -04:00
|
|
|
devtools().delegate().create_child_element(dom_node->tab->description(), dom_node->identifier.id,
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
async_handler<WalkerActor>(message, [](auto& self, auto node_id, auto& response) {
|
2025-03-11 09:47:41 -04:00
|
|
|
JsonArray nodes;
|
2025-03-07 09:19:28 -05:00
|
|
|
|
2025-03-11 09:47:41 -04:00
|
|
|
if (auto actor = self.m_dom_node_id_to_actor_map.get(node_id); actor.has_value()) {
|
|
|
|
if (auto dom_node = WalkerActor::dom_node_for(self, *actor); dom_node.has_value())
|
|
|
|
nodes.must_append(self.serialize_node(dom_node->node));
|
2025-03-11 07:55:05 -04:00
|
|
|
}
|
2025-03-07 09:19:28 -05:00
|
|
|
|
2025-03-11 09:47:41 -04:00
|
|
|
response.set("newParents"sv, JsonArray {});
|
|
|
|
response.set("nodes"sv, move(nodes));
|
|
|
|
}));
|
2025-03-07 09:19:28 -05:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "insertBefore"sv) {
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-03-10 18:01:29 -04:00
|
|
|
return;
|
|
|
|
|
2025-03-11 09:05:01 -04:00
|
|
|
auto parent = get_required_parameter<String>(message, "parent"sv);
|
|
|
|
if (!parent.has_value())
|
2025-03-10 18:01:29 -04:00
|
|
|
return;
|
|
|
|
|
|
|
|
auto dom_node = WalkerActor::dom_node_for(*this, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
if (!dom_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-10 18:01:29 -04:00
|
|
|
auto parent_dom_node = WalkerActor::dom_node_for(*this, *parent);
|
2025-03-11 07:55:05 -04:00
|
|
|
if (!parent_dom_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *parent);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
|
|
|
}
|
2025-03-10 18:01:29 -04:00
|
|
|
|
|
|
|
Optional<Web::UniqueNodeID> sibling_node_id;
|
2025-03-12 08:01:23 -04:00
|
|
|
if (auto sibling = message.data.get_string("sibling"sv); sibling.has_value()) {
|
2025-03-10 18:01:29 -04:00
|
|
|
auto sibling_dom_node = WalkerActor::dom_node_for(*this, *sibling);
|
2025-03-11 07:55:05 -04:00
|
|
|
if (!sibling_dom_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *sibling);
|
2025-03-10 18:01:29 -04:00
|
|
|
return;
|
2025-03-11 07:55:05 -04:00
|
|
|
}
|
2025-03-10 18:01:29 -04:00
|
|
|
|
|
|
|
sibling_node_id = sibling_dom_node->identifier.id;
|
|
|
|
}
|
|
|
|
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
devtools().delegate().insert_dom_node_before(dom_node->tab->description(), dom_node->identifier.id, parent_dom_node->identifier.id, sibling_node_id, default_async_handler(message));
|
2025-03-10 18:01:29 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "isInDOMTree"sv) {
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-03-06 20:08:27 -05:00
|
|
|
return;
|
|
|
|
|
|
|
|
response.set("attached"sv, m_actor_to_dom_node_map.contains(*node));
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_response(message, move(response));
|
2025-03-06 20:08:27 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "outerHTML"sv) {
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-03-10 17:36:41 -04:00
|
|
|
return;
|
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
auto dom_node = WalkerActor::dom_node_for(*this, *node);
|
|
|
|
if (!dom_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
|
|
|
}
|
2025-03-10 17:36:41 -04:00
|
|
|
|
2025-03-11 09:47:41 -04:00
|
|
|
devtools().delegate().get_dom_node_outer_html(dom_node->tab->description(), dom_node->identifier.id,
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
async_handler(message, [](auto&, auto html, auto& response) {
|
2025-03-11 09:47:41 -04:00
|
|
|
response.set("value"sv, move(html));
|
|
|
|
}));
|
2025-03-10 17:36:41 -04:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "previousSibling"sv) {
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-03-06 20:08:27 -05:00
|
|
|
return;
|
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
auto dom_node = WalkerActor::dom_node_for(*this, *node);
|
|
|
|
if (!dom_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
2025-03-06 20:08:27 -05:00
|
|
|
}
|
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
JsonValue previous_sibling;
|
|
|
|
if (auto previous_sibling_node = previous_sibling_for_node(dom_node->node); previous_sibling_node.has_value())
|
|
|
|
previous_sibling = serialize_node(*previous_sibling_node);
|
|
|
|
|
2025-03-06 20:08:27 -05:00
|
|
|
response.set("node"sv, move(previous_sibling));
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_response(message, move(response));
|
2025-03-06 20:08:27 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "querySelector"sv) {
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-02-15 07:57:36 -05:00
|
|
|
return;
|
|
|
|
|
2025-03-11 09:05:01 -04:00
|
|
|
auto selector = get_required_parameter<String>(message, "selector"sv);
|
|
|
|
if (!selector.has_value())
|
2025-02-15 07:57:36 -05:00
|
|
|
return;
|
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
auto ancestor_node = WalkerActor::dom_node_for(*this, *node);
|
|
|
|
if (!ancestor_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
|
|
|
}
|
2025-02-15 07:57:36 -05:00
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
if (auto selected_node = find_node_by_selector(ancestor_node->node, *selector); selected_node.has_value()) {
|
|
|
|
response.set("node"sv, serialize_node(*selected_node));
|
2025-02-15 07:57:36 -05:00
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
if (auto parent = m_dom_node_to_parent_map.get(&selected_node.value()); parent.value() && parent.value() != &ancestor_node->node) {
|
|
|
|
// FIXME: Should this be a stack of nodes leading to `ancestor_node`?
|
|
|
|
JsonArray new_parents;
|
|
|
|
new_parents.must_append(serialize_node(*parent.value()));
|
|
|
|
|
|
|
|
response.set("newParents"sv, move(new_parents));
|
2025-02-15 07:57:36 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_response(message, move(response));
|
2025-02-15 07:57:36 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "removeNode"sv) {
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-03-06 20:08:27 -05:00
|
|
|
return;
|
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
auto dom_node = WalkerActor::dom_node_for(*this, *node);
|
|
|
|
if (!dom_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
|
|
|
}
|
2025-03-06 20:08:27 -05:00
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
JsonValue next_sibling;
|
|
|
|
if (auto next_sibling_node = next_sibling_for_node(dom_node->node); next_sibling_node.has_value())
|
|
|
|
next_sibling = serialize_node(*next_sibling_node);
|
2025-03-06 20:08:27 -05:00
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
auto parent_node = remove_node(dom_node->node);
|
|
|
|
if (!parent_node.has_value())
|
|
|
|
return;
|
2025-03-06 20:08:27 -05:00
|
|
|
|
2025-03-11 09:47:41 -04:00
|
|
|
devtools().delegate().remove_dom_node(dom_node->tab->description(), dom_node->identifier.id,
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
async_handler(message, [next_sibling = move(next_sibling)](auto&, auto, auto& response) mutable {
|
2025-03-11 09:47:41 -04:00
|
|
|
response.set("nextSibling"sv, move(next_sibling));
|
|
|
|
}));
|
2025-03-06 20:08:27 -05:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "retainNode"sv) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_response(message, move(response));
|
2025-03-06 20:08:27 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "setOuterHTML"sv) {
|
2025-03-11 09:05:01 -04:00
|
|
|
auto node = get_required_parameter<String>(message, "node"sv);
|
|
|
|
if (!node.has_value())
|
2025-03-10 17:36:41 -04:00
|
|
|
return;
|
|
|
|
|
2025-03-11 09:05:01 -04:00
|
|
|
auto value = get_required_parameter<String>(message, "value"sv);
|
|
|
|
if (!value.has_value())
|
2025-03-10 17:36:41 -04:00
|
|
|
return;
|
|
|
|
|
2025-03-11 07:55:05 -04:00
|
|
|
auto dom_node = WalkerActor::dom_node_for(*this, *node);
|
|
|
|
if (!dom_node.has_value()) {
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_unknown_actor_error(message, *node);
|
2025-03-11 07:55:05 -04:00
|
|
|
return;
|
|
|
|
}
|
2025-03-10 17:36:41 -04:00
|
|
|
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
devtools().delegate().set_dom_node_outer_html(dom_node->tab->description(), dom_node->identifier.id, value.release_value(), default_async_handler(message));
|
2025-03-10 17:36:41 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
if (message.type == "watchRootNode"sv) {
|
2025-02-15 07:57:36 -05:00
|
|
|
response.set("type"sv, "root-available"sv);
|
|
|
|
response.set("node"sv, serialize_root());
|
LibDevTools: Re-implement how we handle ordered responses
We must reply to requests received from the client in the order they are
received. The wrench in this requirement is handling requests that must
be performed asynchronously, such as fetching the serialized DOM tree
from the WebContent process.
We currently handle this with a "block token". Async request handlers
hold a token that blocks any subsequent responses from being sent. When
that token is removed (i.e. the async request now has a response to be
sent), the async response is then sent followed by the blocked responses
in-order.
This strategy had a limitation that we could not handle an actor trying
to take 2 block tokens, meaning only one async request could be handled
at a time. This has been fine so far, but an upcoming feature (style
sheet sources) will break this limitation. The client will request N
sources at a time, which would try to take N block tokens.
The new strategy is to assign all requests an ID, and store a list of
request IDs that are awaiting a response. When the server wants to send
a reply, we match the ID of the replied-to message to this list of IDs.
If it is not the first in this list, then we are blocked waiting for an
earlier reply, and just store the response. When the earlier request(s)
receive their response, we can then send out all blocked replies (up to
the next request that has not yet received a response).
2025-03-12 12:26:58 -04:00
|
|
|
send_response(message, move(response));
|
2025-02-15 07:57:36 -05:00
|
|
|
|
2025-03-11 08:15:23 -04:00
|
|
|
send_message({});
|
2025-02-15 07:57:36 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-12 08:01:23 -04:00
|
|
|
send_unrecognized_packet_type_error(message);
|
2025-02-15 07:57:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
bool WalkerActor::is_suitable_for_dom_inspection(JsonValue const& node)
|
|
|
|
{
|
|
|
|
if (!node.is_object())
|
|
|
|
return true;
|
|
|
|
|
|
|
|
auto const& object = node.as_object();
|
|
|
|
|
|
|
|
if (!object.has_string("name"sv) || !object.has_string("type"sv))
|
|
|
|
return false;
|
|
|
|
|
2025-02-19 09:28:02 -05:00
|
|
|
if (auto text = object.get_string("text"sv); text.has_value()) {
|
|
|
|
if (AK::StringUtils::is_whitespace(*text))
|
2025-02-15 07:57:36 -05:00
|
|
|
return false;
|
|
|
|
}
|
2025-02-19 09:28:02 -05:00
|
|
|
if (auto data = object.get_string("data"sv); data.has_value()) {
|
|
|
|
if (AK::StringUtils::is_whitespace(*data))
|
2025-02-15 07:57:36 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
JsonValue WalkerActor::serialize_root() const
|
|
|
|
{
|
|
|
|
return serialize_node(m_dom_tree);
|
|
|
|
}
|
|
|
|
|
2025-07-02 12:29:07 -04:00
|
|
|
static constexpr Web::DOM::NodeType parse_node_type(StringView type)
|
|
|
|
{
|
|
|
|
if (type == "document"sv)
|
|
|
|
return Web::DOM::NodeType::DOCUMENT_NODE;
|
|
|
|
if (type == "element"sv)
|
|
|
|
return Web::DOM::NodeType::ELEMENT_NODE;
|
|
|
|
if (type == "text"sv)
|
|
|
|
return Web::DOM::NodeType::TEXT_NODE;
|
|
|
|
if (type == "comment"sv)
|
|
|
|
return Web::DOM::NodeType::COMMENT_NODE;
|
|
|
|
return Web::DOM::NodeType::INVALID;
|
|
|
|
}
|
|
|
|
|
2025-02-15 07:57:36 -05:00
|
|
|
JsonValue WalkerActor::serialize_node(JsonObject const& node) const
|
|
|
|
{
|
|
|
|
auto tab = m_tab.strong_ref();
|
|
|
|
if (!tab)
|
|
|
|
return {};
|
|
|
|
|
2025-02-17 13:21:07 -05:00
|
|
|
auto actor = node.get_string("actor"sv);
|
2025-02-15 07:57:36 -05:00
|
|
|
if (!actor.has_value())
|
|
|
|
return {};
|
|
|
|
|
2025-02-17 13:21:07 -05:00
|
|
|
auto name = node.get_string("name"sv).release_value();
|
|
|
|
auto type = node.get_string("type"sv).release_value();
|
2025-02-15 07:57:36 -05:00
|
|
|
|
2025-07-02 12:29:07 -04:00
|
|
|
auto dom_type = parse_node_type(type);
|
2025-02-15 07:57:36 -05:00
|
|
|
JsonValue node_value;
|
|
|
|
|
|
|
|
auto is_top_level_document = &node == &m_dom_tree;
|
|
|
|
auto is_displayed = !is_top_level_document && node.get_bool("visible"sv).value_or(false);
|
|
|
|
auto is_scrollable = node.get_bool("scrollable"sv).value_or(false);
|
|
|
|
|
2025-07-02 12:29:07 -04:00
|
|
|
auto is_shadow_root = false;
|
|
|
|
auto is_after_pseudo_element = false;
|
|
|
|
auto is_before_pseudo_element = false;
|
|
|
|
auto is_marker_pseudo_element = false;
|
2025-02-15 07:57:36 -05:00
|
|
|
|
2025-07-02 12:29:07 -04:00
|
|
|
if (dom_type == Web::DOM::NodeType::TEXT_NODE) {
|
2025-02-17 13:21:07 -05:00
|
|
|
if (auto text = node.get_string("text"sv); text.has_value())
|
2025-02-15 07:57:36 -05:00
|
|
|
node_value = text.release_value();
|
2025-07-02 12:29:07 -04:00
|
|
|
} else if (dom_type == Web::DOM::NodeType::COMMENT_NODE) {
|
2025-02-17 13:21:07 -05:00
|
|
|
if (auto data = node.get_string("data"sv); data.has_value())
|
2025-02-15 07:57:36 -05:00
|
|
|
node_value = data.release_value();
|
2025-07-02 12:29:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (type == "shadow-root"sv) {
|
2025-02-15 07:57:36 -05:00
|
|
|
is_shadow_root = true;
|
2025-07-02 12:29:07 -04:00
|
|
|
} else if (type == "pseudo-element"sv) {
|
|
|
|
auto pseudo_element = node.get_integer<UnderlyingType<Web::CSS::PseudoElement>>("pseudo-element"sv).map([](auto value) {
|
|
|
|
VERIFY(value < to_underlying(Web::CSS::PseudoElement::KnownPseudoElementCount));
|
|
|
|
return static_cast<Web::CSS::PseudoElement>(value);
|
|
|
|
});
|
|
|
|
|
|
|
|
is_after_pseudo_element = pseudo_element == Web::CSS::PseudoElement::After;
|
|
|
|
is_before_pseudo_element = pseudo_element == Web::CSS::PseudoElement::Before;
|
|
|
|
is_marker_pseudo_element = pseudo_element == Web::CSS::PseudoElement::Marker;
|
|
|
|
|
|
|
|
auto parent_id = node.get_integer<Web::UniqueNodeID::Type>("parent-id"sv).value();
|
|
|
|
|
|
|
|
if (auto parent_actor = m_dom_node_id_to_actor_map.get(parent_id); parent_actor.has_value()) {
|
|
|
|
if (auto parent_node = WalkerActor::dom_node_for(this, *parent_actor); parent_node.has_value()) {
|
|
|
|
dom_type = parse_node_type(parent_node->node.get_string("type"sv).value());
|
|
|
|
is_displayed = !is_top_level_document && parent_node->node.get_bool("visible"sv).value_or(false);
|
|
|
|
}
|
|
|
|
}
|
2025-02-15 07:57:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
size_t child_count = 0;
|
|
|
|
if (auto children = node.get_array("children"sv); children.has_value())
|
|
|
|
child_count = children->size();
|
|
|
|
|
|
|
|
JsonArray attrs;
|
|
|
|
|
|
|
|
if (auto attributes = node.get_object("attributes"sv); attributes.has_value()) {
|
2025-02-17 12:18:27 -05:00
|
|
|
attributes->for_each_member([&](String const& name, JsonValue const& value) {
|
2025-02-15 07:57:36 -05:00
|
|
|
if (!value.is_string())
|
|
|
|
return;
|
|
|
|
|
|
|
|
JsonObject attr;
|
2025-02-17 13:21:07 -05:00
|
|
|
attr.set("name"sv, name);
|
2025-02-15 07:57:36 -05:00
|
|
|
attr.set("value"sv, value.as_string());
|
|
|
|
attrs.must_append(move(attr));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
JsonObject serialized;
|
|
|
|
serialized.set("actor"sv, actor.release_value());
|
|
|
|
serialized.set("attrs"sv, move(attrs));
|
2025-02-19 09:28:02 -05:00
|
|
|
serialized.set("baseURI"sv, tab->description().url);
|
2025-02-15 07:57:36 -05:00
|
|
|
serialized.set("causesOverflow"sv, false);
|
|
|
|
serialized.set("containerType"sv, JsonValue {});
|
2025-02-17 13:21:07 -05:00
|
|
|
serialized.set("displayName"sv, name.to_ascii_lowercase());
|
2025-02-17 11:59:45 -05:00
|
|
|
serialized.set("displayType"sv, "block"sv);
|
2025-03-07 09:19:28 -05:00
|
|
|
serialized.set("hasEventListeners"sv, false);
|
2025-07-02 12:29:07 -04:00
|
|
|
serialized.set("isAfterPseudoElement"sv, is_after_pseudo_element);
|
2025-02-15 07:57:36 -05:00
|
|
|
serialized.set("isAnonymous"sv, false);
|
2025-07-02 12:29:07 -04:00
|
|
|
serialized.set("isBeforePseudoElement"sv, is_before_pseudo_element);
|
2025-02-15 07:57:36 -05:00
|
|
|
serialized.set("isDirectShadowHostChild"sv, JsonValue {});
|
|
|
|
serialized.set("isDisplayed"sv, is_displayed);
|
|
|
|
serialized.set("isInHTMLDocument"sv, true);
|
2025-07-02 12:29:07 -04:00
|
|
|
serialized.set("isMarkerPseudoElement"sv, is_marker_pseudo_element);
|
2025-02-15 07:57:36 -05:00
|
|
|
serialized.set("isNativeAnonymous"sv, false);
|
|
|
|
serialized.set("isScrollable"sv, is_scrollable);
|
|
|
|
serialized.set("isShadowHost"sv, false);
|
|
|
|
serialized.set("isShadowRoot"sv, is_shadow_root);
|
|
|
|
serialized.set("isTopLevelDocument"sv, is_top_level_document);
|
|
|
|
serialized.set("nodeName"sv, name);
|
|
|
|
serialized.set("nodeType"sv, to_underlying(dom_type));
|
|
|
|
serialized.set("nodeValue"sv, move(node_value));
|
|
|
|
serialized.set("numChildren"sv, child_count);
|
|
|
|
serialized.set("shadowRootMode"sv, JsonValue {});
|
|
|
|
serialized.set("traits"sv, JsonObject {});
|
|
|
|
|
2025-03-07 09:19:28 -05:00
|
|
|
// FIXME: De-duplicate this string. LibDevTools currently cannot depend on LibWeb.
|
|
|
|
serialized.set("namespaceURI"sv, "http://www.w3.org/1999/xhtml"sv);
|
|
|
|
|
2025-02-15 07:57:36 -05:00
|
|
|
if (!is_top_level_document) {
|
|
|
|
if (auto parent = m_dom_node_to_parent_map.get(&node); parent.has_value() && parent.value()) {
|
2025-02-17 13:21:07 -05:00
|
|
|
actor = parent.value()->get_string("actor"sv);
|
2025-02-15 07:57:36 -05:00
|
|
|
if (!actor.has_value())
|
|
|
|
return {};
|
|
|
|
|
|
|
|
serialized.set("parent"sv, actor.release_value());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return serialized;
|
|
|
|
}
|
|
|
|
|
2025-03-05 16:16:49 -05:00
|
|
|
Optional<WalkerActor::DOMNode> WalkerActor::dom_node_for(WeakPtr<WalkerActor> const& weak_walker, StringView actor)
|
|
|
|
{
|
|
|
|
if (auto walker = weak_walker.strong_ref())
|
|
|
|
return walker->dom_node(actor);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2025-02-20 08:58:35 -05:00
|
|
|
Optional<WalkerActor::DOMNode> WalkerActor::dom_node(StringView actor)
|
|
|
|
{
|
2025-03-05 16:16:49 -05:00
|
|
|
auto tab = m_tab.strong_ref();
|
|
|
|
if (!tab)
|
|
|
|
return {};
|
|
|
|
|
2025-02-20 08:58:35 -05:00
|
|
|
auto maybe_dom_node = m_actor_to_dom_node_map.get(actor);
|
|
|
|
if (!maybe_dom_node.has_value() || !maybe_dom_node.value())
|
|
|
|
return {};
|
|
|
|
|
|
|
|
auto const& dom_node = *maybe_dom_node.value();
|
2025-03-06 19:49:27 -05:00
|
|
|
auto identifier = NodeIdentifier::for_node(dom_node);
|
2025-02-20 08:58:35 -05:00
|
|
|
|
2025-03-06 19:49:27 -05:00
|
|
|
return DOMNode { .node = dom_node, .identifier = move(identifier), .tab = tab.release_nonnull() };
|
2025-02-20 08:58:35 -05:00
|
|
|
}
|
|
|
|
|
2025-02-15 07:57:36 -05:00
|
|
|
Optional<JsonObject const&> WalkerActor::find_node_by_selector(JsonObject const& node, StringView selector)
|
|
|
|
{
|
|
|
|
auto matches = [&](auto const& candidate) {
|
2025-02-19 09:28:02 -05:00
|
|
|
return candidate.get_string("name"sv)->equals_ignoring_ascii_case(selector);
|
2025-02-15 07:57:36 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
if (matches(node))
|
|
|
|
return node;
|
|
|
|
|
|
|
|
if (auto children = node.get_array("children"sv); children.has_value()) {
|
|
|
|
for (size_t i = 0; i < children->size(); ++i) {
|
|
|
|
auto const& child = children->at(i);
|
|
|
|
|
|
|
|
if (matches(child.as_object()))
|
|
|
|
return child.as_object();
|
|
|
|
|
|
|
|
if (auto result = find_node_by_selector(child.as_object(), selector); result.has_value())
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2025-03-06 20:08:27 -05:00
|
|
|
enum class Direction {
|
|
|
|
Previous,
|
|
|
|
Next,
|
|
|
|
};
|
|
|
|
static Optional<JsonObject const&> sibling_for_node(JsonObject const& parent, JsonObject const& node, Direction direction)
|
|
|
|
{
|
|
|
|
auto children = parent.get_array("children"sv);
|
|
|
|
VERIFY(children.has_value());
|
|
|
|
|
|
|
|
auto index = children->values().find_first_index_if([&](auto const& child) {
|
|
|
|
return &child.as_object() == &node;
|
|
|
|
});
|
|
|
|
VERIFY(index.has_value());
|
|
|
|
|
|
|
|
switch (direction) {
|
|
|
|
case Direction::Previous:
|
|
|
|
if (*index == 0)
|
|
|
|
return {};
|
|
|
|
index = *index - 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Direction::Next:
|
|
|
|
if (*index == children->size() - 1)
|
|
|
|
return {};
|
|
|
|
index = *index + 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return children->at(*index).as_object();
|
|
|
|
}
|
|
|
|
|
|
|
|
Optional<JsonObject const&> WalkerActor::previous_sibling_for_node(JsonObject const& node)
|
|
|
|
{
|
|
|
|
auto parent = m_dom_node_to_parent_map.get(&node);
|
|
|
|
if (!parent.has_value() || !parent.value())
|
|
|
|
return {};
|
|
|
|
return sibling_for_node(*parent.value(), node, Direction::Previous);
|
|
|
|
}
|
|
|
|
|
|
|
|
Optional<JsonObject const&> WalkerActor::next_sibling_for_node(JsonObject const& node)
|
|
|
|
{
|
|
|
|
auto parent = m_dom_node_to_parent_map.get(&node);
|
|
|
|
if (!parent.has_value() || !parent.value())
|
|
|
|
return {};
|
|
|
|
return sibling_for_node(*parent.value(), node, Direction::Next);
|
|
|
|
}
|
|
|
|
|
|
|
|
Optional<JsonObject const&> WalkerActor::remove_node(JsonObject const& node)
|
|
|
|
{
|
|
|
|
auto maybe_parent = m_dom_node_to_parent_map.get(&node);
|
|
|
|
if (!maybe_parent.has_value() || !maybe_parent.value())
|
|
|
|
return {};
|
|
|
|
auto const& parent = *maybe_parent.value();
|
|
|
|
|
|
|
|
auto children = parent.get_array("children"sv);
|
|
|
|
VERIFY(children.has_value());
|
|
|
|
|
|
|
|
const_cast<JsonArray&>(*children).values().remove_first_matching([&](auto const& child) {
|
|
|
|
return &child.as_object() == &node;
|
|
|
|
});
|
|
|
|
|
|
|
|
populate_dom_tree_cache();
|
|
|
|
return parent;
|
|
|
|
}
|
|
|
|
|
2025-03-06 19:56:03 -05:00
|
|
|
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("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;
|
|
|
|
}
|
|
|
|
|
2025-03-06 19:49:27 -05:00
|
|
|
void WalkerActor::populate_dom_tree_cache()
|
|
|
|
{
|
|
|
|
m_dom_node_to_parent_map.clear();
|
|
|
|
m_actor_to_dom_node_map.clear();
|
|
|
|
m_dom_node_id_to_actor_map.clear();
|
|
|
|
|
|
|
|
populate_dom_tree_cache(m_dom_tree, nullptr);
|
|
|
|
}
|
|
|
|
|
2025-02-15 07:57:36 -05:00
|
|
|
void WalkerActor::populate_dom_tree_cache(JsonObject& node, JsonObject const* parent)
|
|
|
|
{
|
2025-03-06 19:49:27 -05:00
|
|
|
auto const& node_actor = actor_for_node(node);
|
|
|
|
node.set("actor"sv, node_actor.name());
|
2025-02-21 15:19:35 -05:00
|
|
|
|
2025-02-15 07:57:36 -05:00
|
|
|
m_dom_node_to_parent_map.set(&node, parent);
|
2025-02-21 15:19:35 -05:00
|
|
|
m_actor_to_dom_node_map.set(node_actor.name(), &node);
|
2025-03-06 19:49:27 -05:00
|
|
|
|
|
|
|
if (!node_actor.node_identifier().pseudo_element.has_value())
|
|
|
|
m_dom_node_id_to_actor_map.set(node_actor.node_identifier().id, node_actor.name());
|
2025-02-15 07:57:36 -05:00
|
|
|
|
|
|
|
auto children = node.get_array("children"sv);
|
|
|
|
if (!children.has_value())
|
|
|
|
return;
|
|
|
|
|
|
|
|
children->values().remove_all_matching([&](JsonValue const& child) {
|
|
|
|
return !is_suitable_for_dom_inspection(child);
|
|
|
|
});
|
|
|
|
|
|
|
|
children->for_each([&](JsonValue& child) {
|
|
|
|
populate_dom_tree_cache(child.as_object(), &node);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-03-06 19:49:27 -05:00
|
|
|
NodeActor const& WalkerActor::actor_for_node(JsonObject const& node)
|
|
|
|
{
|
|
|
|
auto identifier = NodeIdentifier::for_node(node);
|
|
|
|
|
2025-03-13 16:59:38 -04:00
|
|
|
if (auto it = m_node_actors.find(identifier); it != m_node_actors.end()) {
|
|
|
|
if (auto node_actor = it->value.strong_ref())
|
2025-03-06 19:49:27 -05:00
|
|
|
return *node_actor;
|
|
|
|
}
|
|
|
|
|
2025-07-02 15:15:33 +01:00
|
|
|
auto& node_actor = devtools().register_actor<NodeActor>(identifier, *this);
|
2025-03-13 16:59:38 -04:00
|
|
|
m_node_actors.set(identifier, node_actor);
|
|
|
|
|
|
|
|
return node_actor;
|
2025-03-06 19:49:27 -05:00
|
|
|
}
|
|
|
|
|
2025-02-15 07:57:36 -05:00
|
|
|
}
|