DevTools server callbacks can outlive the server when deferred work is queued during connection shutdown. Capture weak pointers for those tasks and clear socket callbacks while the server is being destroyed so later actor cleanup cannot touch freed state. Add protocol coverage that destroys a server with deferred actor cleanup pending, before adding the style-rule actor path that depends on this.
158 lines
4.3 KiB
C++
158 lines
4.3 KiB
C++
/*
|
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Debug.h>
|
|
#include <AK/JsonObject.h>
|
|
#include <AK/JsonValue.h>
|
|
#include <LibCore/EventLoop.h>
|
|
#include <LibCore/Socket.h>
|
|
#include <LibCore/TCPServer.h>
|
|
#include <LibDevTools/Actors/DeviceActor.h>
|
|
#include <LibDevTools/Actors/ParentAccessibilityActor.h>
|
|
#include <LibDevTools/Actors/PreferenceActor.h>
|
|
#include <LibDevTools/Actors/ProcessActor.h>
|
|
#include <LibDevTools/Actors/TabActor.h>
|
|
#include <LibDevTools/Connection.h>
|
|
#include <LibDevTools/DevToolsServer.h>
|
|
|
|
namespace DevTools {
|
|
|
|
static u64 s_server_count = 0;
|
|
|
|
ErrorOr<NonnullOwnPtr<DevToolsServer>> DevToolsServer::create(DevToolsDelegate& delegate, u16 port)
|
|
{
|
|
auto address = IPv4Address::from_string("0.0.0.0"sv).release_value();
|
|
|
|
auto server = TRY(Core::TCPServer::try_create());
|
|
TRY(server->listen(address, port, Core::TCPServer::AllowAddressReuse::Yes));
|
|
|
|
return adopt_own(*new DevToolsServer(delegate, move(server)));
|
|
}
|
|
|
|
DevToolsServer::DevToolsServer(DevToolsDelegate& delegate, NonnullRefPtr<Core::TCPServer> server)
|
|
: m_server(move(server))
|
|
, m_delegate(delegate)
|
|
, m_server_id(s_server_count++)
|
|
{
|
|
m_server->on_ready_to_accept = [weak_self = make_weak_ptr<DevToolsServer>()] {
|
|
if (!weak_self)
|
|
return;
|
|
|
|
if (auto result = weak_self->on_new_client(); result.is_error())
|
|
warnln("Failed to accept DevTools client: {}", result.error());
|
|
};
|
|
}
|
|
|
|
DevToolsServer::~DevToolsServer()
|
|
{
|
|
m_is_shutting_down = true;
|
|
m_server->on_ready_to_accept = {};
|
|
|
|
if (m_connection) {
|
|
m_connection->on_connection_closed = {};
|
|
m_connection->on_message_received = {};
|
|
}
|
|
}
|
|
|
|
Optional<u16> DevToolsServer::local_port() const
|
|
{
|
|
return m_server->local_port();
|
|
}
|
|
|
|
void DevToolsServer::refresh_tab_list()
|
|
{
|
|
if (!m_root_actor)
|
|
return;
|
|
|
|
m_actor_registry.remove_all_matching([](auto const&, auto const& actor) {
|
|
return is<TabActor>(*actor);
|
|
});
|
|
|
|
m_root_actor->send_tab_list_changed_message();
|
|
}
|
|
|
|
void DevToolsServer::unregister_actor(String const& name)
|
|
{
|
|
if (m_is_shutting_down)
|
|
return;
|
|
|
|
Core::deferred_invoke([weak_self = make_weak_ptr<DevToolsServer>(), name] {
|
|
if (weak_self)
|
|
weak_self->m_actor_registry.remove(name);
|
|
});
|
|
}
|
|
|
|
ErrorOr<void> DevToolsServer::on_new_client()
|
|
{
|
|
if (m_connection)
|
|
return Error::from_string_literal("Only one active DevTools connection is currently allowed");
|
|
|
|
auto client = TRY(m_server->accept());
|
|
auto buffered_socket = TRY(Core::BufferedTCPSocket::create(move(client)));
|
|
|
|
m_connection = Connection::create(move(buffered_socket));
|
|
|
|
m_connection->on_connection_closed = [weak_self = make_weak_ptr<DevToolsServer>()] {
|
|
if (weak_self)
|
|
weak_self->close_connection();
|
|
};
|
|
|
|
m_connection->on_message_received = [weak_self = make_weak_ptr<DevToolsServer>()](auto message) {
|
|
if (weak_self)
|
|
weak_self->on_message_received(move(message));
|
|
};
|
|
|
|
m_root_actor = register_actor<RootActor>();
|
|
|
|
register_actor<DeviceActor>();
|
|
register_actor<PreferenceActor>();
|
|
register_actor<ProcessActor>(ProcessDescription { .is_parent = true });
|
|
register_actor<ParentAccessibilityActor>();
|
|
|
|
return {};
|
|
}
|
|
|
|
void DevToolsServer::on_message_received(JsonObject message)
|
|
{
|
|
auto to = message.get_string("to"sv);
|
|
if (!to.has_value()) {
|
|
m_root_actor->send_missing_parameter_error({}, "to"sv);
|
|
return;
|
|
}
|
|
|
|
auto actor = m_actor_registry.find(*to);
|
|
if (actor == m_actor_registry.end()) {
|
|
m_root_actor->send_unknown_actor_error({}, *to);
|
|
return;
|
|
}
|
|
|
|
auto type = message.get_string("type"sv);
|
|
if (!type.has_value()) {
|
|
actor->value->send_missing_parameter_error({}, "type"sv);
|
|
return;
|
|
}
|
|
|
|
actor->value->message_received(*type, move(message));
|
|
}
|
|
|
|
void DevToolsServer::close_connection()
|
|
{
|
|
dbgln_if(DEVTOOLS_DEBUG, "Lost connection to the DevTools client");
|
|
|
|
if (m_is_shutting_down)
|
|
return;
|
|
|
|
Core::deferred_invoke([weak_self = make_weak_ptr<DevToolsServer>()] {
|
|
if (!weak_self)
|
|
return;
|
|
|
|
weak_self->m_connection = nullptr;
|
|
weak_self->m_actor_registry.clear();
|
|
weak_self->m_root_actor = nullptr;
|
|
});
|
|
}
|
|
|
|
}
|