ladybird/Libraries/LibDevTools/Actor.cpp
Timothy Flynn 58bc44ba2a 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.
2025-02-19 08:45:51 -05:00

111 lines
3.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/JsonObject.h>
#include <LibDevTools/Actor.h>
#include <LibDevTools/Connection.h>
#include <LibDevTools/DevToolsServer.h>
namespace DevTools {
Actor::Actor(DevToolsServer& devtools, ByteString name)
: m_devtools(devtools)
, m_name(move(name))
{
}
Actor::~Actor() = default;
void Actor::send_message(JsonValue message, Optional<BlockToken> block_token)
{
if (m_block_responses && !block_token.has_value()) {
m_blocked_responses.append(move(message));
return;
}
if (auto& connection = devtools().connection())
connection->send_message(message);
}
// https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#error-packets
void Actor::send_missing_parameter_error(StringView parameter)
{
JsonObject error;
error.set("from"sv, name());
error.set("error"sv, "missingParameter"sv);
error.set("message"sv, ByteString::formatted("Missing parameter: '{}'", parameter));
send_message(move(error));
}
// https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#error-packets
void Actor::send_unrecognized_packet_type_error(StringView type)
{
JsonObject error;
error.set("from"sv, name());
error.set("error"sv, "unrecognizedPacketType"sv);
error.set("message"sv, ByteString::formatted("Unrecognized packet type: '{}'", type));
send_message(move(error));
}
// https://github.com/mozilla/gecko-dev/blob/master/devtools/server/actors/object.js
// This error is not documented, but is used by Firefox nonetheless.
void Actor::send_unknown_actor_error(StringView actor)
{
JsonObject error;
error.set("from"sv, name());
error.set("error"sv, "unknownActor"sv);
error.set("message"sv, ByteString::formatted("Unknown actor: '{}'", actor));
send_message(move(error));
}
Actor::BlockToken Actor::block_responses()
{
// https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#the-request-reply-pattern
// The actor processes packets in the order they are received, and the client can trust that the ith reply
// corresponds to the ith request.
// The above requirement gets tricky for actors which require an async implementation. For example, the "getWalker"
// message sent to the InspectorActor results in the server fetching the DOM tree as JSON from the WebContent process.
// We cannot reply to the message until that is received. However, we will likely receive more messages from the
// client in that time. We cannot reply to those messages until we've replied to the "getWalker" message. Thus, we
// use this token to queue responses from the actor until that reply can be sent.
return { {}, *this };
}
Actor::BlockToken::BlockToken(Badge<Actor>, Actor& actor)
: m_actor(actor)
{
// If we end up in a situtation where an actor has multiple async handlers at once, we will need to come up with a
// more sophisticated blocking mechanism.
VERIFY(!actor.m_block_responses);
actor.m_block_responses = true;
}
Actor::BlockToken::BlockToken(BlockToken&& other)
: m_actor(move(other.m_actor))
{
}
Actor::BlockToken& Actor::BlockToken::operator=(BlockToken&& other)
{
m_actor = move(other.m_actor);
return *this;
}
Actor::BlockToken::~BlockToken()
{
auto actor = m_actor.strong_ref();
if (!actor)
return;
auto blocked_responses = move(actor->m_blocked_responses);
actor->m_block_responses = false;
for (auto& message : blocked_responses)
actor->send_message(move(message));
}
}