mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 04:09:13 +00:00
LibDevTools: Introduce a Firefox DevTools server library
To aid with debugging web page issues in Ladybird without needing to implement a fully fledged inspector, we can implement the Firefox DevTools protocol and use their DevTools. The protocol is described here: https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html This commit contains just enough to connect to Ladybird from a DevTools client.
This commit is contained in:
parent
49c93d01db
commit
58bc44ba2a
Notes:
github-actions[bot]
2025-02-19 13:47:24 +00:00
Author: https://github.com/trflynn89
Commit: 58bc44ba2a
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3589
Reviewed-by: https://github.com/ADKaster
20 changed files with 947 additions and 0 deletions
101
Libraries/LibDevTools/Connection.cpp
Normal file
101
Libraries/LibDevTools/Connection.cpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 <AK/StringBuilder.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<StringBuilder>();
|
||||
|
||||
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.length(), 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)]() {
|
||||
if (on_message_received)
|
||||
on_message_received(message.as_object());
|
||||
});
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue