diff --git a/Libraries/LibDevTools/Actors/RootActor.cpp b/Libraries/LibDevTools/Actors/RootActor.cpp index 8a8f4ecdcd9..f71927e2d71 100644 --- a/Libraries/LibDevTools/Actors/RootActor.cpp +++ b/Libraries/LibDevTools/Actors/RootActor.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -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("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(*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(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; +} + } diff --git a/Libraries/LibDevTools/Actors/RootActor.h b/Libraries/LibDevTools/Actors/RootActor.h index 5ffa5c534b9..218a54f5758 100644 --- a/Libraries/LibDevTools/Actors/RootActor.h +++ b/Libraries/LibDevTools/Actors/RootActor.h @@ -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 }; }; } diff --git a/Libraries/LibDevTools/Actors/TabActor.cpp b/Libraries/LibDevTools/Actors/TabActor.cpp new file mode 100644 index 00000000000..b9ccf903c91 --- /dev/null +++ b/Libraries/LibDevTools/Actors/TabActor.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace DevTools { + +NonnullRefPtr 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; +} + +} diff --git a/Libraries/LibDevTools/Actors/TabActor.h b/Libraries/LibDevTools/Actors/TabActor.h new file mode 100644 index 00000000000..d92ce708b88 --- /dev/null +++ b/Libraries/LibDevTools/Actors/TabActor.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +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 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; +}; + +} diff --git a/Libraries/LibDevTools/CMakeLists.txt b/Libraries/LibDevTools/CMakeLists.txt index 646a8e3b179..f7f957f018f 100644 --- a/Libraries/LibDevTools/CMakeLists.txt +++ b/Libraries/LibDevTools/CMakeLists.txt @@ -4,6 +4,7 @@ set(SOURCES Actors/PreferenceActor.cpp Actors/ProcessActor.cpp Actors/RootActor.cpp + Actors/TabActor.cpp Connection.cpp DevToolsServer.cpp ) diff --git a/Libraries/LibDevTools/DevToolsDelegate.h b/Libraries/LibDevTools/DevToolsDelegate.h index 4f0d36dedb6..f01ce677368 100644 --- a/Libraries/LibDevTools/DevToolsDelegate.h +++ b/Libraries/LibDevTools/DevToolsDelegate.h @@ -6,6 +6,8 @@ #pragma once +#include +#include #include namespace DevTools { @@ -13,6 +15,8 @@ namespace DevTools { class DevToolsDelegate { public: virtual ~DevToolsDelegate() = default; + + virtual Vector tab_list() const { return {}; } }; } diff --git a/Libraries/LibDevTools/DevToolsServer.cpp b/Libraries/LibDevTools/DevToolsServer.cpp index 5cdc6a642f9..4f21949b322 100644 --- a/Libraries/LibDevTools/DevToolsServer.cpp +++ b/Libraries/LibDevTools/DevToolsServer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -43,6 +44,18 @@ DevToolsServer::DevToolsServer(DevToolsDelegate& delegate, NonnullRefPtr(*actor); + }); + + m_root_actor->send_tab_list_changed_message(); +} + ErrorOr DevToolsServer::on_new_client() { if (m_connection) diff --git a/Libraries/LibDevTools/DevToolsServer.h b/Libraries/LibDevTools/DevToolsServer.h index 3634c53769b..87190ba8398 100644 --- a/Libraries/LibDevTools/DevToolsServer.h +++ b/Libraries/LibDevTools/DevToolsServer.h @@ -45,6 +45,8 @@ public: return actor; } + void refresh_tab_list(); + private: explicit DevToolsServer(DevToolsDelegate&, NonnullRefPtr); diff --git a/Libraries/LibDevTools/Forward.h b/Libraries/LibDevTools/Forward.h index bcd39935cdf..b38b23198bf 100644 --- a/Libraries/LibDevTools/Forward.h +++ b/Libraries/LibDevTools/Forward.h @@ -16,7 +16,9 @@ class DevToolsServer; class PreferenceActor; class ProcessActor; class RootActor; +class TabActor; struct ProcessDescription; +struct TabDescription; }