LibDevTools: Implement enough of the protocol to see a tab list

Previously, we could connect to our DevTools server from Firefox, but
could not see any information on Ladybird's opened tabs. This implements
enough of the protocol to see a tab list, but we cannot yet inspect the
tabs.
This commit is contained in:
Timothy Flynn 2025-02-15 07:53:57 -05:00 committed by Tim Flynn
commit b974e91731
Notes: github-actions[bot] 2025-02-19 13:47:18 +00:00
9 changed files with 168 additions and 2 deletions

View file

@ -9,6 +9,7 @@
#include <LibDevTools/Actors/PreferenceActor.h>
#include <LibDevTools/Actors/ProcessActor.h>
#include <LibDevTools/Actors/RootActor.h>
#include <LibDevTools/Actors/TabActor.h>
#include <LibDevTools/DevToolsDelegate.h>
#include <LibDevTools/DevToolsServer.h>
@ -88,7 +89,23 @@ void RootActor::handle_message(StringView type, JsonObject const& message)
}
if (type == "getTab"sv) {
response.set("tab"sv, JsonObject {});
auto browser_id = message.get_integer<u64>("browserId"sv);
if (!browser_id.has_value()) {
send_missing_parameter_error("browserId"sv);
return;
}
for (auto const& actor : devtools().actor_registry()) {
auto const* tab_actor = as_if<TabActor>(*actor.value);
if (!tab_actor)
continue;
if (tab_actor->description().id != *browser_id)
continue;
response.set("tab"sv, tab_actor->serialize_description());
break;
}
send_message(move(response));
return;
}
@ -119,7 +136,16 @@ void RootActor::handle_message(StringView type, JsonObject const& message)
}
if (type == "listTabs"sv) {
response.set("tabs"sv, JsonArray {});
m_has_sent_tab_list_changed_since_last_list_tabs_request = false;
JsonArray tabs;
for (auto& tab_description : devtools().delegate().tab_list()) {
auto& actor = devtools().register_actor<TabActor>(move(tab_description));
tabs.must_append(actor.serialize_description());
}
response.set("tabs"sv, move(tabs));
send_message(move(response));
return;
}
@ -133,4 +159,17 @@ void RootActor::handle_message(StringView type, JsonObject const& message)
send_unrecognized_packet_type_error(type);
}
void RootActor::send_tab_list_changed_message()
{
if (m_has_sent_tab_list_changed_since_last_list_tabs_request)
return;
JsonObject message;
message.set("from"sv, name());
message.set("type"sv, "tabListChanged"sv);
send_message(move(message));
m_has_sent_tab_list_changed_since_last_list_tabs_request = true;
}
}

View file

@ -20,8 +20,14 @@ public:
virtual void handle_message(StringView type, JsonObject const&) override;
void send_tab_list_changed_message();
private:
RootActor(DevToolsServer&, ByteString name);
// https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#the-request-reply-notify-pattern
// the root actor sends at most one "tabListChanged" notification after each "listTabs" request.
bool m_has_sent_tab_list_changed_since_last_list_tabs_request { false };
};
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/JsonObject.h>
#include <LibDevTools/Actors/TabActor.h>
#include <LibDevTools/DevToolsServer.h>
namespace DevTools {
NonnullRefPtr<TabActor> TabActor::create(DevToolsServer& devtools, ByteString name, TabDescription description)
{
return adopt_ref(*new TabActor(devtools, move(name), move(description)));
}
TabActor::TabActor(DevToolsServer& devtools, ByteString name, TabDescription description)
: Actor(devtools, move(name))
, m_description(move(description))
{
}
TabActor::~TabActor() = default;
void TabActor::handle_message(StringView type, JsonObject const&)
{
JsonObject response;
response.set("from"sv, name());
if (type == "getFavicon"sv) {
// FIXME: Firefox DevTools wants a favicon URL here, but supplying a URL seems to prevent this tab from being
// listed on the about:debugging page. Both Servo and Firefox itself supply `null` here.
response.set("favicon"sv, JsonValue {});
send_message(move(response));
return;
}
send_unrecognized_packet_type_error(type);
}
JsonObject TabActor::serialize_description() const
{
JsonObject traits;
traits.set("watcher"sv, true);
traits.set("supportsReloadDescriptor"sv, true);
// FIXME: We are using the tab's ID multiple times here. This is likely not correct, as both Firefox and Servo
// provide different IDs for browserId, browsingContextID, and outerWindowID.
JsonObject description;
description.set("actor"sv, name());
description.set("title"sv, m_description.title);
description.set("url"sv, m_description.url);
description.set("browserId"sv, m_description.id);
description.set("browsingContextID"sv, m_description.id);
description.set("outerWindowID"sv, m_description.id);
description.set("traits"sv, move(traits));
return description;
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullRefPtr.h>
#include <LibDevTools/Actor.h>
namespace DevTools {
struct TabDescription {
u64 id { 0 };
ByteString title;
ByteString url;
};
class TabActor final : public Actor {
public:
static constexpr auto base_name = "tab"sv;
static NonnullRefPtr<TabActor> create(DevToolsServer&, ByteString name, TabDescription);
virtual ~TabActor() override;
virtual void handle_message(StringView type, JsonObject const&) override;
TabDescription const& description() const { return m_description; }
JsonObject serialize_description() const;
private:
TabActor(DevToolsServer&, ByteString name, TabDescription);
TabDescription m_description;
};
}

View file

@ -4,6 +4,7 @@ set(SOURCES
Actors/PreferenceActor.cpp
Actors/ProcessActor.cpp
Actors/RootActor.cpp
Actors/TabActor.cpp
Connection.cpp
DevToolsServer.cpp
)

View file

@ -6,6 +6,8 @@
#pragma once
#include <AK/Vector.h>
#include <LibDevTools/Actors/TabActor.h>
#include <LibDevTools/Forward.h>
namespace DevTools {
@ -13,6 +15,8 @@ namespace DevTools {
class DevToolsDelegate {
public:
virtual ~DevToolsDelegate() = default;
virtual Vector<TabDescription> tab_list() const { return {}; }
};
}

View file

@ -13,6 +13,7 @@
#include <LibDevTools/Actors/DeviceActor.h>
#include <LibDevTools/Actors/PreferenceActor.h>
#include <LibDevTools/Actors/ProcessActor.h>
#include <LibDevTools/Actors/TabActor.h>
#include <LibDevTools/Connection.h>
#include <LibDevTools/DevToolsServer.h>
@ -43,6 +44,18 @@ DevToolsServer::DevToolsServer(DevToolsDelegate& delegate, NonnullRefPtr<Core::T
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)

View file

@ -45,6 +45,8 @@ public:
return actor;
}
void refresh_tab_list();
private:
explicit DevToolsServer(DevToolsDelegate&, NonnullRefPtr<Core::TCPServer>);

View file

@ -16,7 +16,9 @@ class DevToolsServer;
class PreferenceActor;
class ProcessActor;
class RootActor;
class TabActor;
struct ProcessDescription;
struct TabDescription;
}