ladybird/Libraries/LibDevTools/DevToolsServer.cpp
Sam Atkins 41b4292447 LibDevTools+LibWebView: Implement initial accessibility tree view
This is enough for Firefox to display the Accessibility tab, containing
our accessibility tree which can be inspected. Most information is
blank for now.

There's quite a bit of duplication between AccessibilityWalkerActor and
WalkerActor - it might be worth trying to make a base class once the
details are figured out. Frustratingly, the two don't work quite the
same: for a lot of messages that would be sent to WalkerActor, the
accessibility equivalent is sent to the AccessibilityNodeActor instead.

Co-authored-by: Tim Flynn <trflynn89@pm.me>
2025-10-20 10:51:19 +01:00

122 lines
3.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 = [this]() {
if (auto result = on_new_client(); result.is_error())
warnln("Failed to accept DevTools client: {}", result.error());
};
}
DevToolsServer::~DevToolsServer() = default;
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();
}
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 = [this]() {
close_connection();
};
m_connection->on_message_received = [this](auto message) {
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");
Core::deferred_invoke([this]() {
m_connection = nullptr;
m_actor_registry.clear();
m_root_actor = nullptr;
});
}
}