mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-25 14:05:15 +00:00
This is to prepare for an upcoming change where we will need to track replies to messages by ID. We will be able to add parameters to this structure without having to edit every single actor subclass header file.
100 lines
3 KiB
C++
100 lines
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 <LibDevTools/Connection.h>
|
|
|
|
namespace DevTools {
|
|
|
|
NonnullRefPtr<Connection> Connection::create(NonnullOwnPtr<Core::BufferedTCPSocket> socket)
|
|
{
|
|
return adopt_ref(*new Connection(move(socket)));
|
|
}
|
|
|
|
Connection::Connection(NonnullOwnPtr<Core::BufferedTCPSocket> socket)
|
|
: m_socket(move(socket))
|
|
{
|
|
m_socket->on_ready_to_read = [this]() {
|
|
if (auto result = on_ready_to_read(); result.is_error()) {
|
|
if (on_connection_closed)
|
|
on_connection_closed();
|
|
}
|
|
};
|
|
}
|
|
|
|
Connection::~Connection() = default;
|
|
|
|
// https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#packets
|
|
void Connection::send_message(JsonValue const& message)
|
|
{
|
|
auto serialized = message.serialized();
|
|
|
|
if constexpr (DEVTOOLS_DEBUG) {
|
|
if (message.is_object() && message.as_object().get("error"sv).has_value())
|
|
dbgln("\x1b[1;31m<<\x1b[0m {}", serialized);
|
|
else
|
|
dbgln("\x1b[1;32m<<\x1b[0m {}", serialized);
|
|
}
|
|
|
|
if (m_socket->write_formatted("{}:{}", serialized.byte_count(), serialized).is_error()) {
|
|
if (on_connection_closed)
|
|
on_connection_closed();
|
|
}
|
|
}
|
|
|
|
// https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#packets
|
|
ErrorOr<JsonValue> Connection::read_message()
|
|
{
|
|
ByteBuffer length_buffer;
|
|
|
|
// FIXME: `read_until(':')` would be nicer here, but that seems to return immediately without receiving any data.
|
|
while (true) {
|
|
auto byte = TRY(m_socket->read_value<u8>());
|
|
if (byte == ':') {
|
|
break;
|
|
}
|
|
|
|
length_buffer.append(byte);
|
|
}
|
|
|
|
auto length = StringView { length_buffer }.to_number<size_t>();
|
|
if (!length.has_value())
|
|
return Error::from_string_literal("Could not read message length from DevTools client");
|
|
|
|
ByteBuffer message_buffer;
|
|
message_buffer.resize(*length);
|
|
|
|
TRY(m_socket->read_until_filled(message_buffer));
|
|
|
|
auto message = TRY(JsonValue::from_string(message_buffer));
|
|
dbgln_if(DEVTOOLS_DEBUG, "\x1b[1;33m>>\x1b[0m {}", message);
|
|
|
|
return message;
|
|
}
|
|
|
|
ErrorOr<void> Connection::on_ready_to_read()
|
|
{
|
|
// https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#the-request-reply-pattern
|
|
// Note that it is correct for a client to send several requests to a request/reply actor without waiting for a
|
|
// reply to each request before sending the next; requests can be pipelined.
|
|
while (TRY(m_socket->can_read_without_blocking())) {
|
|
auto message = TRY(read_message());
|
|
if (!message.is_object())
|
|
continue;
|
|
|
|
Core::deferred_invoke([this, message = move(message)]() mutable {
|
|
if (on_message_received)
|
|
on_message_received(move(message.as_object()));
|
|
});
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
}
|