diff --git a/Libraries/LibDevTools/Actors/CSSPropertiesActor.cpp b/Libraries/LibDevTools/Actors/CSSPropertiesActor.cpp new file mode 100644 index 00000000000..2b3ad4016de --- /dev/null +++ b/Libraries/LibDevTools/Actors/CSSPropertiesActor.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace DevTools { + +NonnullRefPtr CSSPropertiesActor::create(DevToolsServer& devtools, ByteString name) +{ + return adopt_ref(*new CSSPropertiesActor(devtools, move(name))); +} + +CSSPropertiesActor::CSSPropertiesActor(DevToolsServer& devtools, ByteString name) + : Actor(devtools, move(name)) +{ +} + +CSSPropertiesActor::~CSSPropertiesActor() = default; + +void CSSPropertiesActor::handle_message(StringView type, JsonObject const&) +{ + JsonObject response; + response.set("from"sv, name()); + + if (type == "getCSSDatabase"sv) { + auto css_property_list = devtools().delegate().css_property_list(); + + JsonObject properties; + + for (auto const& css_property : css_property_list) { + JsonArray subproperties; + subproperties.must_append(css_property.name); + + JsonObject property; + property.set("isInherited"sv, css_property.is_inherited); + property.set("supports"sv, JsonArray {}); + property.set("values"sv, JsonArray {}); + property.set("subproperties"sv, move(subproperties)); + + properties.set(css_property.name, move(property)); + } + + response.set("properties"sv, move(properties)); + send_message(move(response)); + return; + } + + send_unrecognized_packet_type_error(type); +} + +} diff --git a/Libraries/LibDevTools/Actors/CSSPropertiesActor.h b/Libraries/LibDevTools/Actors/CSSPropertiesActor.h new file mode 100644 index 00000000000..fec5763f844 --- /dev/null +++ b/Libraries/LibDevTools/Actors/CSSPropertiesActor.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace DevTools { + +struct CSSProperty { + ByteString name; + bool is_inherited { false }; +}; + +class CSSPropertiesActor final : public Actor { +public: + static constexpr auto base_name = "css-properties"sv; + + static NonnullRefPtr create(DevToolsServer&, ByteString name); + virtual ~CSSPropertiesActor() override; + + virtual void handle_message(StringView type, JsonObject const&) override; + +private: + CSSPropertiesActor(DevToolsServer&, ByteString name); +}; + +} diff --git a/Libraries/LibDevTools/Actors/FrameActor.cpp b/Libraries/LibDevTools/Actors/FrameActor.cpp new file mode 100644 index 00000000000..07f557c8ffa --- /dev/null +++ b/Libraries/LibDevTools/Actors/FrameActor.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace DevTools { + +NonnullRefPtr FrameActor::create(DevToolsServer& devtools, ByteString name, WeakPtr tab, WeakPtr css_properties, WeakPtr inspector, WeakPtr thread) +{ + return adopt_ref(*new FrameActor(devtools, move(name), move(tab), move(css_properties), move(inspector), move(thread))); +} + +FrameActor::FrameActor(DevToolsServer& devtools, ByteString name, WeakPtr tab, WeakPtr css_properties, WeakPtr inspector, WeakPtr thread) + : Actor(devtools, move(name)) + , m_tab(move(tab)) + , m_css_properties(move(css_properties)) + , m_inspector(move(inspector)) + , m_thread(move(thread)) +{ +} + +FrameActor::~FrameActor() = default; + +void FrameActor::handle_message(StringView type, JsonObject const&) +{ + JsonObject response; + response.set("from"sv, name()); + + if (type == "listFrames"sv) { + send_message(move(response)); + return; + } + + send_unrecognized_packet_type_error(type); +} + +void FrameActor::send_frame_update_message() +{ + JsonArray frames; + + if (auto tab_actor = m_tab.strong_ref()) { + JsonObject frame; + frame.set("id"sv, tab_actor->description().id); + frame.set("title"sv, tab_actor->description().title); + frame.set("url"sv, tab_actor->description().url); + frames.must_append(move(frame)); + } + + JsonObject message; + message.set("from", name()); + message.set("type", "frameUpdate"sv); + message.set("frames", move(frames)); + send_message(move(message)); +} + +JsonObject FrameActor::serialize_target() const +{ + JsonObject traits; + traits.set("frames"sv, true); + traits.set("isBrowsingContext"sv, true); + traits.set("logInPage"sv, false); + traits.set("navigation"sv, true); + traits.set("supportsTopLevelTargetFlag"sv, true); + traits.set("watchpoints"sv, true); + + JsonObject target; + target.set("actor"sv, name()); + + if (auto tab_actor = m_tab.strong_ref()) { + target.set("title"sv, tab_actor->description().title); + target.set("url"sv, tab_actor->description().url); + target.set("browsingContextID"sv, tab_actor->description().id); + target.set("outerWindowID"sv, tab_actor->description().id); + target.set("isTopLevelTarget"sv, true); + } + + target.set("traits"sv, move(traits)); + + if (auto css_properties = m_css_properties.strong_ref()) + target.set("cssPropertiesActor"sv, css_properties->name()); + if (auto inspector = m_inspector.strong_ref()) + target.set("inspectorActor"sv, inspector->name()); + if (auto thread = m_thread.strong_ref()) + target.set("threadActor"sv, thread->name()); + + return target; +} + +} diff --git a/Libraries/LibDevTools/Actors/FrameActor.h b/Libraries/LibDevTools/Actors/FrameActor.h new file mode 100644 index 00000000000..a8b06198d04 --- /dev/null +++ b/Libraries/LibDevTools/Actors/FrameActor.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace DevTools { + +class FrameActor final : public Actor { +public: + static constexpr auto base_name = "frame"sv; + + static NonnullRefPtr create(DevToolsServer&, ByteString name, WeakPtr, WeakPtr, WeakPtr, WeakPtr); + virtual ~FrameActor() override; + + virtual void handle_message(StringView type, JsonObject const&) override; + void send_frame_update_message(); + + JsonObject serialize_target() const; + +private: + FrameActor(DevToolsServer&, ByteString name, WeakPtr, WeakPtr, WeakPtr, WeakPtr); + + WeakPtr m_tab; + + WeakPtr m_css_properties; + WeakPtr m_inspector; + WeakPtr m_thread; +}; + +} diff --git a/Libraries/LibDevTools/Actors/HighlighterActor.cpp b/Libraries/LibDevTools/Actors/HighlighterActor.cpp new file mode 100644 index 00000000000..081c8ea6c9f --- /dev/null +++ b/Libraries/LibDevTools/Actors/HighlighterActor.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace DevTools { + +NonnullRefPtr HighlighterActor::create(DevToolsServer& devtools, ByteString name) +{ + return adopt_ref(*new HighlighterActor(devtools, move(name))); +} + +HighlighterActor::HighlighterActor(DevToolsServer& devtools, ByteString name) + : Actor(devtools, move(name)) +{ +} + +HighlighterActor::~HighlighterActor() = default; + +void HighlighterActor::handle_message(StringView type, JsonObject const&) +{ + JsonObject response; + response.set("from"sv, name()); + + if (type == "show"sv) { + response.set("value"sv, true); + send_message(move(response)); + return; + } + + if (type == "hide"sv) { + send_message(move(response)); + return; + } + + send_unrecognized_packet_type_error(type); +} + +JsonValue HighlighterActor::serialize_highlighter() const +{ + JsonObject highlighter; + highlighter.set("actor"sv, name()); + return highlighter; +} + +} diff --git a/Libraries/LibDevTools/Actors/HighlighterActor.h b/Libraries/LibDevTools/Actors/HighlighterActor.h new file mode 100644 index 00000000000..d34888f0bf1 --- /dev/null +++ b/Libraries/LibDevTools/Actors/HighlighterActor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace DevTools { + +class HighlighterActor final : public Actor { +public: + static constexpr auto base_name = "highlighter"sv; + + static NonnullRefPtr create(DevToolsServer&, ByteString name); + virtual ~HighlighterActor() override; + + virtual void handle_message(StringView type, JsonObject const&) override; + JsonValue serialize_highlighter() const; + +private: + HighlighterActor(DevToolsServer&, ByteString name); +}; + +} diff --git a/Libraries/LibDevTools/Actors/InspectorActor.cpp b/Libraries/LibDevTools/Actors/InspectorActor.cpp new file mode 100644 index 00000000000..fbb040c531e --- /dev/null +++ b/Libraries/LibDevTools/Actors/InspectorActor.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DevTools { + +NonnullRefPtr InspectorActor::create(DevToolsServer& devtools, ByteString name, WeakPtr tab) +{ + return adopt_ref(*new InspectorActor(devtools, move(name), move(tab))); +} + +InspectorActor::InspectorActor(DevToolsServer& devtools, ByteString name, WeakPtr tab) + : Actor(devtools, move(name)) + , m_tab(move(tab)) +{ +} + +InspectorActor::~InspectorActor() = default; + +void InspectorActor::handle_message(StringView type, JsonObject const&) +{ + JsonObject response; + response.set("from"sv, name()); + + if (type == "getPageStyle"sv) { + if (!m_page_style) + m_page_style = devtools().register_actor(); + + response.set("pageStyle"sv, m_page_style->serialize_style()); + send_message(move(response)); + return; + } + + if (type == "getHighlighterByType"sv) { + if (!m_highlighter) + m_highlighter = devtools().register_actor(); + + response.set("highlighter"sv, m_highlighter->serialize_highlighter()); + send_message(move(response)); + return; + } + + if (type == "getWalker"sv) { + if (auto tab = m_tab.strong_ref()) { + auto block_token = block_responses(); + + devtools().delegate().inspect_tab(tab->description(), + [weak_self = make_weak_ptr(), block_token = move(block_token)](ErrorOr dom_tree) mutable { + if (dom_tree.is_error()) { + dbgln_if(DEVTOOLS_DEBUG, "Unable to retrieve DOM tree: {}", dom_tree.error()); + return; + } + if (!WalkerActor::is_suitable_for_dom_inspection(dom_tree.value())) { + dbgln_if(DEVTOOLS_DEBUG, "Did not receive a suitable DOM tree: {}", dom_tree); + return; + } + + if (auto self = weak_self.strong_ref()) + self->received_dom_tree(move(dom_tree.release_value().as_object()), move(block_token)); + }); + } + + return; + } + + send_unrecognized_packet_type_error(type); +} + +void InspectorActor::received_dom_tree(JsonObject dom_tree, BlockToken block_token) +{ + auto& walker_actor = devtools().register_actor(m_tab, move(dom_tree)); + + JsonObject walker; + walker.set("actor"sv, walker_actor.name()); + walker.set("root"sv, walker_actor.serialize_root()); + + JsonObject message; + message.set("from"sv, name()); + message.set("walker"sv, move(walker)); + send_message(move(message), move(block_token)); +} + +} diff --git a/Libraries/LibDevTools/Actors/InspectorActor.h b/Libraries/LibDevTools/Actors/InspectorActor.h new file mode 100644 index 00000000000..d62b7da3123 --- /dev/null +++ b/Libraries/LibDevTools/Actors/InspectorActor.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace DevTools { + +class InspectorActor final : public Actor { +public: + static constexpr auto base_name = "inspector"sv; + + static NonnullRefPtr create(DevToolsServer&, ByteString name, WeakPtr); + virtual ~InspectorActor() override; + + virtual void handle_message(StringView type, JsonObject const&) override; + +private: + InspectorActor(DevToolsServer&, ByteString name, WeakPtr); + + void received_dom_tree(JsonObject, BlockToken); + + WeakPtr m_tab; + WeakPtr m_page_style; + WeakPtr m_highlighter; +}; + +} diff --git a/Libraries/LibDevTools/Actors/PageStyleActor.cpp b/Libraries/LibDevTools/Actors/PageStyleActor.cpp new file mode 100644 index 00000000000..e584b8de90d --- /dev/null +++ b/Libraries/LibDevTools/Actors/PageStyleActor.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace DevTools { + +NonnullRefPtr PageStyleActor::create(DevToolsServer& devtools, ByteString name) +{ + return adopt_ref(*new PageStyleActor(devtools, move(name))); +} + +PageStyleActor::PageStyleActor(DevToolsServer& devtools, ByteString name) + : Actor(devtools, move(name)) +{ +} + +PageStyleActor::~PageStyleActor() = default; + +void PageStyleActor::handle_message(StringView type, JsonObject const&) +{ + send_unrecognized_packet_type_error(type); +} + +JsonValue PageStyleActor::serialize_style() const +{ + JsonObject traits; + traits.set("fontStyleLevel4"sv, true); + traits.set("fontWeightLevel4"sv, true); + traits.set("fontStretchLevel4"sv, true); + traits.set("fontVariations"sv, true); + + JsonObject style; + style.set("actor"sv, name()); + style.set("traits"sv, move(traits)); + return style; +} + +} diff --git a/Libraries/LibDevTools/Actors/PageStyleActor.h b/Libraries/LibDevTools/Actors/PageStyleActor.h new file mode 100644 index 00000000000..133024f4fad --- /dev/null +++ b/Libraries/LibDevTools/Actors/PageStyleActor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace DevTools { + +class PageStyleActor final : public Actor { +public: + static constexpr auto base_name = "page-style"sv; + + static NonnullRefPtr create(DevToolsServer&, ByteString name); + virtual ~PageStyleActor() override; + + virtual void handle_message(StringView type, JsonObject const&) override; + JsonValue serialize_style() const; + +private: + PageStyleActor(DevToolsServer&, ByteString name); +}; + +} diff --git a/Libraries/LibDevTools/Actors/TabActor.cpp b/Libraries/LibDevTools/Actors/TabActor.cpp index b9ccf903c91..b7a25fef02d 100644 --- a/Libraries/LibDevTools/Actors/TabActor.cpp +++ b/Libraries/LibDevTools/Actors/TabActor.cpp @@ -6,6 +6,7 @@ #include #include +#include #include namespace DevTools { @@ -36,6 +37,16 @@ void TabActor::handle_message(StringView type, JsonObject const&) return; } + if (type == "getWatcher"sv) { + if (!m_watcher) + m_watcher = devtools().register_actor(this); + + response.set("actor"sv, m_watcher->name()); + response.set("traits"sv, m_watcher->serialize_description()); + send_message(move(response)); + return; + } + send_unrecognized_packet_type_error(type); } diff --git a/Libraries/LibDevTools/Actors/TabActor.h b/Libraries/LibDevTools/Actors/TabActor.h index d92ce708b88..fd42725306f 100644 --- a/Libraries/LibDevTools/Actors/TabActor.h +++ b/Libraries/LibDevTools/Actors/TabActor.h @@ -33,6 +33,7 @@ private: TabActor(DevToolsServer&, ByteString name, TabDescription); TabDescription m_description; + WeakPtr m_watcher; }; } diff --git a/Libraries/LibDevTools/Actors/TargetConfigurationActor.cpp b/Libraries/LibDevTools/Actors/TargetConfigurationActor.cpp new file mode 100644 index 00000000000..fe575126cda --- /dev/null +++ b/Libraries/LibDevTools/Actors/TargetConfigurationActor.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace DevTools { + +NonnullRefPtr TargetConfigurationActor::create(DevToolsServer& devtools, ByteString name) +{ + return adopt_ref(*new TargetConfigurationActor(devtools, move(name))); +} + +TargetConfigurationActor::TargetConfigurationActor(DevToolsServer& devtools, ByteString name) + : Actor(devtools, move(name)) +{ +} + +TargetConfigurationActor::~TargetConfigurationActor() = default; + +void TargetConfigurationActor::handle_message(StringView type, JsonObject const& message) +{ + JsonObject response; + response.set("from"sv, name()); + + if (type == "updateConfiguration"sv) { + auto configuration = message.get_object("configuration"sv); + if (!configuration.has_value()) { + send_missing_parameter_error("configuration"sv); + return; + } + + send_message(move(response)); + return; + } + + send_unrecognized_packet_type_error(type); +} + +JsonObject TargetConfigurationActor::serialize_configuration() const +{ + JsonObject supported_options; + supported_options.set("cacheDisabled"sv, false); + supported_options.set("colorSchemeSimulation"sv, false); + supported_options.set("customFormatters"sv, false); + supported_options.set("customUserAgent"sv, false); + supported_options.set("javascriptEnabled"sv, false); + supported_options.set("overrideDPPX"sv, false); + supported_options.set("printSimulationEnabled"sv, false); + supported_options.set("rdmPaneMaxTouchPoints"sv, false); + supported_options.set("rdmPaneOrientation"sv, false); + supported_options.set("recordAllocations"sv, false); + supported_options.set("reloadOnTouchSimulationToggle"sv, false); + supported_options.set("restoreFocus"sv, false); + supported_options.set("serviceWorkersTestingEnabled"sv, false); + supported_options.set("setTabOffline"sv, false); + supported_options.set("touchEventsOverride"sv, false); + supported_options.set("tracerOptions"sv, false); + supported_options.set("useSimpleHighlightersForReducedMotion"sv, false); + + JsonObject traits; + traits.set("supportedOptions"sv, move(supported_options)); + + JsonObject target; + target.set("actor"sv, name()); + target.set("configuration"sv, JsonObject {}); + target.set("traits"sv, move(traits)); + + return target; +} + +} diff --git a/Libraries/LibDevTools/Actors/TargetConfigurationActor.h b/Libraries/LibDevTools/Actors/TargetConfigurationActor.h new file mode 100644 index 00000000000..6c0de3b410b --- /dev/null +++ b/Libraries/LibDevTools/Actors/TargetConfigurationActor.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace DevTools { + +class TargetConfigurationActor final : public Actor { +public: + static constexpr auto base_name = "target-configuration"sv; + + static NonnullRefPtr create(DevToolsServer&, ByteString name); + virtual ~TargetConfigurationActor() override; + + virtual void handle_message(StringView type, JsonObject const&) override; + + JsonObject serialize_configuration() const; + +private: + TargetConfigurationActor(DevToolsServer&, ByteString name); +}; + +} diff --git a/Libraries/LibDevTools/Actors/ThreadActor.cpp b/Libraries/LibDevTools/Actors/ThreadActor.cpp new file mode 100644 index 00000000000..efa968707ad --- /dev/null +++ b/Libraries/LibDevTools/Actors/ThreadActor.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace DevTools { + +NonnullRefPtr ThreadActor::create(DevToolsServer& devtools, ByteString name) +{ + return adopt_ref(*new ThreadActor(devtools, move(name))); +} + +ThreadActor::ThreadActor(DevToolsServer& devtools, ByteString name) + : Actor(devtools, move(name)) +{ +} + +ThreadActor::~ThreadActor() = default; + +void ThreadActor::handle_message(StringView type, JsonObject const&) +{ + send_unrecognized_packet_type_error(type); +} + +} diff --git a/Libraries/LibDevTools/Actors/ThreadActor.h b/Libraries/LibDevTools/Actors/ThreadActor.h new file mode 100644 index 00000000000..7688f996c55 --- /dev/null +++ b/Libraries/LibDevTools/Actors/ThreadActor.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace DevTools { + +class ThreadActor final : public Actor { +public: + static constexpr auto base_name = "thread"sv; + + static NonnullRefPtr create(DevToolsServer&, ByteString name); + virtual ~ThreadActor() override; + + virtual void handle_message(StringView type, JsonObject const&) override; + +private: + ThreadActor(DevToolsServer&, ByteString name); +}; + +} diff --git a/Libraries/LibDevTools/Actors/ThreadConfigurationActor.cpp b/Libraries/LibDevTools/Actors/ThreadConfigurationActor.cpp new file mode 100644 index 00000000000..a53e6bfb3c6 --- /dev/null +++ b/Libraries/LibDevTools/Actors/ThreadConfigurationActor.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace DevTools { + +NonnullRefPtr ThreadConfigurationActor::create(DevToolsServer& devtools, ByteString name) +{ + return adopt_ref(*new ThreadConfigurationActor(devtools, move(name))); +} + +ThreadConfigurationActor::ThreadConfigurationActor(DevToolsServer& devtools, ByteString name) + : Actor(devtools, move(name)) +{ +} + +ThreadConfigurationActor::~ThreadConfigurationActor() = default; + +void ThreadConfigurationActor::handle_message(StringView type, JsonObject const& message) +{ + JsonObject response; + response.set("from"sv, name()); + + if (type == "updateConfiguration"sv) { + auto configuration = message.get_object("configuration"sv); + if (!configuration.has_value()) { + send_missing_parameter_error("configuration"sv); + return; + } + + send_message(move(response)); + return; + } + + send_unrecognized_packet_type_error(type); +} + +JsonObject ThreadConfigurationActor::serialize_configuration() const +{ + JsonObject target; + target.set("actor"sv, name()); + + return target; +} + +} diff --git a/Libraries/LibDevTools/Actors/ThreadConfigurationActor.h b/Libraries/LibDevTools/Actors/ThreadConfigurationActor.h new file mode 100644 index 00000000000..b0e77b2cd44 --- /dev/null +++ b/Libraries/LibDevTools/Actors/ThreadConfigurationActor.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace DevTools { + +class ThreadConfigurationActor final : public Actor { +public: + static constexpr auto base_name = "thread-configuration"sv; + + static NonnullRefPtr create(DevToolsServer&, ByteString name); + virtual ~ThreadConfigurationActor() override; + + virtual void handle_message(StringView type, JsonObject const&) override; + + JsonObject serialize_configuration() const; + +private: + ThreadConfigurationActor(DevToolsServer&, ByteString name); +}; + +} diff --git a/Libraries/LibDevTools/Actors/WalkerActor.cpp b/Libraries/LibDevTools/Actors/WalkerActor.cpp new file mode 100644 index 00000000000..328440510e8 --- /dev/null +++ b/Libraries/LibDevTools/Actors/WalkerActor.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace DevTools { + +NonnullRefPtr WalkerActor::create(DevToolsServer& devtools, ByteString name, WeakPtr tab, JsonObject dom_tree) +{ + return adopt_ref(*new WalkerActor(devtools, move(name), move(tab), move(dom_tree))); +} + +WalkerActor::WalkerActor(DevToolsServer& devtools, ByteString name, WeakPtr tab, JsonObject dom_tree) + : Actor(devtools, move(name)) + , m_tab(move(tab)) + , m_dom_tree(move(dom_tree)) +{ + populate_dom_tree_cache(m_dom_tree); +} + +WalkerActor::~WalkerActor() = default; + +void WalkerActor::handle_message(StringView type, JsonObject const& message) +{ + JsonObject response; + response.set("from"sv, name()); + + if (type == "children"sv) { + auto node = message.get_byte_string("node"sv); + if (!node.has_value()) { + send_missing_parameter_error("node"sv); + return; + } + + JsonArray nodes; + + if (auto ancestor_node = m_actor_to_dom_node_map.get(*node); ancestor_node.has_value()) { + if (auto children = ancestor_node.value()->get_array("children"sv); children.has_value()) { + + children->for_each([&](JsonValue const& child) { + nodes.must_append(serialize_node(child.as_object())); + }); + } + } + + response.set("hasFirst"sv, !nodes.is_empty()); + response.set("hasLast"sv, !nodes.is_empty()); + response.set("nodes"sv, move(nodes)); + send_message(move(response)); + return; + } + + if (type == "querySelector"sv) { + auto node = message.get_byte_string("node"sv); + if (!node.has_value()) { + send_missing_parameter_error("node"sv); + return; + } + + auto selector = message.get_byte_string("selector"sv); + if (!selector.has_value()) { + send_missing_parameter_error("selector"sv); + return; + } + + if (auto ancestor_node = m_actor_to_dom_node_map.get(*node); ancestor_node.has_value()) { + if (auto selected_node = find_node_by_selector(*ancestor_node.value(), *selector); selected_node.has_value()) { + response.set("node"sv, serialize_node(*selected_node)); + + if (auto parent = m_dom_node_to_parent_map.get(&selected_node.value()); parent.value() && parent.value() != ancestor_node.value()) { + // FIXME: Should this be a stack of nodes leading to `ancestor_node`? + JsonArray new_parents; + new_parents.must_append(serialize_node(*parent.value())); + + response.set("newParents"sv, move(new_parents)); + } + } + } + + send_message(move(response)); + return; + } + + if (type == "watchRootNode"sv) { + response.set("type"sv, "root-available"sv); + response.set("node"sv, serialize_root()); + send_message(move(response)); + + JsonObject message; + message.set("from", name()); + send_message(move(message)); + + return; + } + + send_unrecognized_packet_type_error(type); +} + +bool WalkerActor::is_suitable_for_dom_inspection(JsonValue const& node) +{ + if (!node.is_object()) + return true; + + auto const& object = node.as_object(); + + if (!object.has_string("name"sv) || !object.has_string("type"sv)) + return false; + + if (auto text = object.get_byte_string("text"sv); text.has_value()) { + if (text->is_whitespace()) + return false; + } + if (auto data = object.get_byte_string("data"sv); data.has_value()) { + if (data->is_whitespace()) + return false; + } + + return true; +} + +JsonValue WalkerActor::serialize_root() const +{ + return serialize_node(m_dom_tree); +} + +JsonValue WalkerActor::serialize_node(JsonObject const& node) const +{ + auto tab = m_tab.strong_ref(); + if (!tab) + return {}; + + auto actor = node.get_byte_string("actor"sv); + if (!actor.has_value()) + return {}; + + auto name = node.get_byte_string("name"sv).release_value(); + auto type = node.get_byte_string("type"sv).release_value(); + + auto dom_type = Web::DOM::NodeType::INVALID; + JsonValue node_value; + + auto is_top_level_document = &node == &m_dom_tree; + auto is_displayed = !is_top_level_document && node.get_bool("visible"sv).value_or(false); + auto is_scrollable = node.get_bool("scrollable"sv).value_or(false); + auto is_shadow_root = false; + + if (type == "document"sv) { + dom_type = Web::DOM::NodeType::DOCUMENT_NODE; + } else if (type == "element"sv) { + dom_type = Web::DOM::NodeType::ELEMENT_NODE; + } else if (type == "text"sv) { + dom_type = Web::DOM::NodeType::TEXT_NODE; + + if (auto text = node.get_byte_string("text"sv); text.has_value()) + node_value = text.release_value(); + } else if (type == "comment"sv) { + dom_type = Web::DOM::NodeType::COMMENT_NODE; + + if (auto data = node.get_byte_string("data"sv); data.has_value()) + node_value = data.release_value(); + } else if (type == "shadow-root"sv) { + is_shadow_root = true; + } + + size_t child_count = 0; + if (auto children = node.get_array("children"sv); children.has_value()) + child_count = children->size(); + + JsonArray attrs; + + if (auto attributes = node.get_object("attributes"sv); attributes.has_value()) { + attributes->for_each_member([&](ByteString const& name, JsonValue const& value) { + if (!value.is_string()) + return; + + JsonObject attr; + attr.set("name"sv, name); + attr.set("value"sv, value.as_string()); + attrs.must_append(move(attr)); + }); + } + + JsonObject serialized; + serialized.set("actor"sv, actor.release_value()); + serialized.set("attrs"sv, move(attrs)); + serialized.set("baseURI"sv, tab->description().url); + serialized.set("causesOverflow"sv, false); + serialized.set("containerType"sv, JsonValue {}); + serialized.set("displayName"sv, name.to_lowercase()); + serialized.set("displayType"sv, "block"); + serialized.set("host"sv, JsonValue {}); + serialized.set("isAfterPseudoElement"sv, false); + serialized.set("isAnonymous"sv, false); + serialized.set("isBeforePseudoElement"sv, false); + serialized.set("isDirectShadowHostChild"sv, JsonValue {}); + serialized.set("isDisplayed"sv, is_displayed); + serialized.set("isInHTMLDocument"sv, true); + serialized.set("isMarkerPseudoElement"sv, false); + serialized.set("isNativeAnonymous"sv, false); + serialized.set("isScrollable"sv, is_scrollable); + serialized.set("isShadowHost"sv, false); + serialized.set("isShadowRoot"sv, is_shadow_root); + serialized.set("isTopLevelDocument"sv, is_top_level_document); + serialized.set("nodeName"sv, name); + serialized.set("nodeType"sv, to_underlying(dom_type)); + serialized.set("nodeValue"sv, move(node_value)); + serialized.set("numChildren"sv, child_count); + serialized.set("shadowRootMode"sv, JsonValue {}); + serialized.set("traits"sv, JsonObject {}); + + if (!is_top_level_document) { + if (auto parent = m_dom_node_to_parent_map.get(&node); parent.has_value() && parent.value()) { + actor = parent.value()->get_byte_string("actor"sv); + if (!actor.has_value()) + return {}; + + serialized.set("parent"sv, actor.release_value()); + } + } + + return serialized; +} + +Optional WalkerActor::find_node_by_selector(JsonObject const& node, StringView selector) +{ + auto matches = [&](auto const& candidate) { + return candidate.get_byte_string("name"sv)->equals_ignoring_ascii_case(selector); + }; + + if (matches(node)) + return node; + + if (auto children = node.get_array("children"sv); children.has_value()) { + for (size_t i = 0; i < children->size(); ++i) { + auto const& child = children->at(i); + + if (matches(child.as_object())) + return child.as_object(); + + if (auto result = find_node_by_selector(child.as_object(), selector); result.has_value()) + return result; + } + } + + return {}; +} + +void WalkerActor::populate_dom_tree_cache(JsonObject& node, JsonObject const* parent) +{ + m_dom_node_to_parent_map.set(&node, parent); + + auto actor = ByteString::formatted("{}-node{}", name(), m_dom_node_count++); + m_actor_to_dom_node_map.set(actor, &node); + node.set("actor"sv, actor); + + auto children = node.get_array("children"sv); + if (!children.has_value()) + return; + + children->values().remove_all_matching([&](JsonValue const& child) { + return !is_suitable_for_dom_inspection(child); + }); + + children->for_each([&](JsonValue& child) { + populate_dom_tree_cache(child.as_object(), &node); + }); +} + +} diff --git a/Libraries/LibDevTools/Actors/WalkerActor.h b/Libraries/LibDevTools/Actors/WalkerActor.h new file mode 100644 index 00000000000..87bc8bb0234 --- /dev/null +++ b/Libraries/LibDevTools/Actors/WalkerActor.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace DevTools { + +class WalkerActor final : public Actor { +public: + static constexpr auto base_name = "walker"sv; + + static NonnullRefPtr create(DevToolsServer&, ByteString name, WeakPtr, JsonObject dom_tree); + virtual ~WalkerActor() override; + + virtual void handle_message(StringView type, JsonObject const&) override; + + static bool is_suitable_for_dom_inspection(JsonValue const&); + JsonValue serialize_root() const; + +private: + WalkerActor(DevToolsServer&, ByteString name, WeakPtr, JsonObject dom_tree); + + JsonValue serialize_node(JsonObject const&) const; + Optional find_node_by_selector(JsonObject const& node, StringView selector); + + void populate_dom_tree_cache(JsonObject& node, JsonObject const* parent = nullptr); + + WeakPtr m_tab; + JsonObject m_dom_tree; + + HashMap m_dom_node_to_parent_map; + HashMap m_actor_to_dom_node_map; + size_t m_dom_node_count { 0 }; +}; + +} diff --git a/Libraries/LibDevTools/Actors/WatcherActor.cpp b/Libraries/LibDevTools/Actors/WatcherActor.cpp new file mode 100644 index 00000000000..9e0bd5bcbcf --- /dev/null +++ b/Libraries/LibDevTools/Actors/WatcherActor.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DevTools { + +NonnullRefPtr WatcherActor::create(DevToolsServer& devtools, ByteString name, WeakPtr tab) +{ + return adopt_ref(*new WatcherActor(devtools, move(name), move(tab))); +} + +WatcherActor::WatcherActor(DevToolsServer& devtools, ByteString name, WeakPtr tab) + : Actor(devtools, move(name)) + , m_tab(move(tab)) +{ +} + +WatcherActor::~WatcherActor() = default; + +void WatcherActor::handle_message(StringView type, JsonObject const& message) +{ + JsonObject response; + response.set("from"sv, name()); + + if (type == "getParentBrowsingContextID"sv) { + auto browsing_context_id = message.get_integer("browsingContextID"sv); + if (!browsing_context_id.has_value()) { + send_missing_parameter_error("browsingContextID"sv); + return; + } + + response.set("browsingContextID"sv, *browsing_context_id); + send_message(move(response)); + return; + } + + if (type == "getTargetConfigurationActor"sv) { + if (!m_target_configuration) + m_target_configuration = devtools().register_actor(); + + response.set("configuration"sv, m_target_configuration->serialize_configuration()); + send_message(move(response)); + return; + } + + if (type == "getThreadConfigurationActor"sv) { + if (!m_thread_configuration) + m_thread_configuration = devtools().register_actor(); + + response.set("configuration"sv, m_thread_configuration->serialize_configuration()); + send_message(move(response)); + return; + } + + if (type == "watchTargets"sv) { + auto target_type = message.get_byte_string("targetType"sv); + if (!target_type.has_value()) { + send_missing_parameter_error("targetType"sv); + return; + } + + if (target_type == "frame"sv) { + auto& css_properties = devtools().register_actor(); + auto& inspector = devtools().register_actor(m_tab); + auto& thread = devtools().register_actor(); + + auto& target = devtools().register_actor(m_tab, css_properties, inspector, thread); + m_target = target; + + response.set("type"sv, "target-available-form"sv); + response.set("target"sv, target.serialize_target()); + send_message(move(response)); + + target.send_frame_update_message(); + + JsonObject message; + message.set("from", name()); + send_message(move(message)); + + return; + } + } + + send_unrecognized_packet_type_error(type); +} + +JsonObject WatcherActor::serialize_description() const +{ + JsonObject resources; + resources.set("Cache"sv, false); + resources.set("console-message"sv, false); + resources.set("cookies"sv, false); + resources.set("css-change"sv, false); + resources.set("css-message"sv, false); + resources.set("css-registered-properties"sv, false); + resources.set("document-event"sv, false); + resources.set("error-message"sv, false); + resources.set("extension-storage"sv, false); + resources.set("indexed-db"sv, false); + resources.set("jstracer-state"sv, false); + resources.set("jstracer-trace"sv, false); + resources.set("last-private-context-exit"sv, false); + resources.set("local-storage"sv, false); + resources.set("network-event"sv, false); + resources.set("network-event-stacktrace"sv, false); + resources.set("platform-message"sv, false); + resources.set("reflow"sv, false); + resources.set("server-sent-event"sv, false); + resources.set("session-storage"sv, false); + resources.set("source"sv, false); + resources.set("stylesheet"sv, false); + resources.set("thread-state"sv, false); + resources.set("websocket"sv, false); + + JsonObject description; + description.set("shared_worker"sv, false); + description.set("service_worker"sv, false); + description.set("frame"sv, true); + description.set("process"sv, false); + description.set("worker"sv, false); + description.set("resources"sv, move(resources)); + + return description; +} + +} diff --git a/Libraries/LibDevTools/Actors/WatcherActor.h b/Libraries/LibDevTools/Actors/WatcherActor.h new file mode 100644 index 00000000000..8bc9886f5ec --- /dev/null +++ b/Libraries/LibDevTools/Actors/WatcherActor.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace DevTools { + +class WatcherActor final : public Actor { +public: + static constexpr auto base_name = "watcher"sv; + + static NonnullRefPtr create(DevToolsServer&, ByteString name, WeakPtr); + virtual ~WatcherActor() override; + + virtual void handle_message(StringView type, JsonObject const&) override; + + JsonObject serialize_description() const; + +private: + WatcherActor(DevToolsServer&, ByteString name, WeakPtr); + + WeakPtr m_tab; + WeakPtr m_target; + WeakPtr m_target_configuration; + WeakPtr m_thread_configuration; +}; + +} diff --git a/Libraries/LibDevTools/CMakeLists.txt b/Libraries/LibDevTools/CMakeLists.txt index f7f957f018f..4d6d2bb73e8 100644 --- a/Libraries/LibDevTools/CMakeLists.txt +++ b/Libraries/LibDevTools/CMakeLists.txt @@ -1,10 +1,20 @@ set(SOURCES Actor.cpp + Actors/CSSPropertiesActor.cpp Actors/DeviceActor.cpp + Actors/FrameActor.cpp + Actors/HighlighterActor.cpp + Actors/InspectorActor.cpp + Actors/PageStyleActor.cpp Actors/PreferenceActor.cpp Actors/ProcessActor.cpp Actors/RootActor.cpp Actors/TabActor.cpp + Actors/TargetConfigurationActor.cpp + Actors/ThreadActor.cpp + Actors/ThreadConfigurationActor.cpp + Actors/WalkerActor.cpp + Actors/WatcherActor.cpp Connection.cpp DevToolsServer.cpp ) diff --git a/Libraries/LibDevTools/DevToolsDelegate.h b/Libraries/LibDevTools/DevToolsDelegate.h index f01ce677368..41c5c38c228 100644 --- a/Libraries/LibDevTools/DevToolsDelegate.h +++ b/Libraries/LibDevTools/DevToolsDelegate.h @@ -6,7 +6,11 @@ #pragma once +#include +#include +#include #include +#include #include #include @@ -17,6 +21,10 @@ public: virtual ~DevToolsDelegate() = default; virtual Vector tab_list() const { return {}; } + virtual Vector css_property_list() const { return {}; } + + using OnTabInspectionComplete = Function)>; + virtual void inspect_tab(TabDescription const&, OnTabInspectionComplete) const { } }; } diff --git a/Libraries/LibDevTools/Forward.h b/Libraries/LibDevTools/Forward.h index b38b23198bf..71442c1de2e 100644 --- a/Libraries/LibDevTools/Forward.h +++ b/Libraries/LibDevTools/Forward.h @@ -10,14 +10,25 @@ namespace DevTools { class Actor; class Connection; +class CSSPropertiesActor; class DeviceActor; class DevToolsDelegate; class DevToolsServer; +class FrameActor; +class HighlighterActor; +class InspectorActor; +class PageStyleActor; class PreferenceActor; class ProcessActor; class RootActor; class TabActor; +class TargetConfigurationActor; +class ThreadActor; +class ThreadConfigurationActor; +class WalkerActor; +class WatcherActor; +struct CSSProperty; struct ProcessDescription; struct TabDescription;