diff --git a/Libraries/LibDevTools/Actors/FrameActor.cpp b/Libraries/LibDevTools/Actors/FrameActor.cpp index 36c49d708cd..e413386d048 100644 --- a/Libraries/LibDevTools/Actors/FrameActor.cpp +++ b/Libraries/LibDevTools/Actors/FrameActor.cpp @@ -4,12 +4,14 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include #include #include #include +#include #include #include #include @@ -18,17 +20,18 @@ namespace DevTools { -NonnullRefPtr FrameActor::create(DevToolsServer& devtools, String name, WeakPtr tab, WeakPtr css_properties, WeakPtr console, WeakPtr inspector, WeakPtr thread) +NonnullRefPtr FrameActor::create(DevToolsServer& devtools, String name, WeakPtr tab, WeakPtr css_properties, WeakPtr console, WeakPtr inspector, WeakPtr style_sheets, WeakPtr thread) { - return adopt_ref(*new FrameActor(devtools, move(name), move(tab), move(css_properties), move(console), move(inspector), move(thread))); + return adopt_ref(*new FrameActor(devtools, move(name), move(tab), move(css_properties), move(console), move(inspector), move(style_sheets), move(thread))); } -FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr tab, WeakPtr css_properties, WeakPtr console, WeakPtr inspector, WeakPtr thread) +FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr tab, WeakPtr css_properties, WeakPtr console, WeakPtr inspector, WeakPtr style_sheets, WeakPtr thread) : Actor(devtools, move(name)) , m_tab(move(tab)) , m_css_properties(move(css_properties)) , m_console(move(console)) , m_inspector(move(inspector)) + , m_style_sheets(move(style_sheets)) , m_thread(move(thread)) { if (auto tab = m_tab.strong_ref()) { @@ -42,6 +45,12 @@ FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr if (auto self = weak_self.strong_ref()) self->console_messages_received(start_index, move(console_output)); }); + + // FIXME: We should adopt WebContent to inform us when style sheets are available or removed. + devtools.delegate().retrieve_style_sheets(tab->description(), + async_handler({}, [](auto& self, auto style_sheets, auto& response) { + self.style_sheets_available(response, move(style_sheets)); + })); } } @@ -59,6 +68,7 @@ void FrameActor::handle_message(Message const& message) if (auto tab = m_tab.strong_ref()) { devtools().delegate().stop_listening_for_dom_mutations(tab->description()); devtools().delegate().stop_listening_for_console_messages(tab->description()); + devtools().delegate().stop_listening_for_style_sheet_sources(tab->description()); tab->reset_selected_node(); } @@ -121,12 +131,78 @@ JsonObject FrameActor::serialize_target() const target.set("consoleActor"sv, console->name()); if (auto inspector = m_inspector.strong_ref()) target.set("inspectorActor"sv, inspector->name()); + if (auto style_sheets = m_style_sheets.strong_ref()) + target.set("styleSheetsActor"sv, style_sheets->name()); if (auto thread = m_thread.strong_ref()) target.set("threadActor"sv, thread->name()); return target; } +void FrameActor::style_sheets_available(JsonObject& response, Vector style_sheets) +{ + JsonArray sheets; + + String tab_url; + if (auto tab_actor = m_tab.strong_ref()) + tab_url = tab_actor->description().url; + + auto style_sheets_actor = m_style_sheets.strong_ref(); + if (!style_sheets_actor) + return; + + for (auto const& [i, style_sheet] : enumerate(style_sheets)) { + auto resource_id = MUST(String::formatted("{}-stylesheet:{}", style_sheets_actor->name(), i)); + + JsonValue href; + JsonValue source_map_base_url; + JsonValue title; + + if (style_sheet.url.has_value()) { + // LibWeb sets the URL to a style sheet name for UA style sheets. DevTools would reject these invalid URLs. + if (style_sheet.type == Web::CSS::StyleSheetIdentifier::Type::UserAgent) { + title = *style_sheet.url; + source_map_base_url = tab_url; + } else { + href = *style_sheet.url; + source_map_base_url = *style_sheet.url; + } + } else { + source_map_base_url = tab_url; + } + + JsonObject sheet; + sheet.set("atRules"sv, JsonArray {}); + sheet.set("constructed"sv, false); + sheet.set("disabled"sv, false); + sheet.set("fileName"sv, JsonValue {}); + sheet.set("href"sv, move(href)); + sheet.set("isNew"sv, false); + sheet.set("nodeHref"sv, tab_url); + sheet.set("resourceId"sv, move(resource_id)); + sheet.set("ruleCount"sv, style_sheet.rule_count); + sheet.set("sourceMapBaseURL"sv, move(source_map_base_url)); + sheet.set("sourceMapURL"sv, ""sv); + sheet.set("styleSheetIndex"sv, i); + sheet.set("system"sv, false); + sheet.set("title"sv, move(title)); + + sheets.must_append(move(sheet)); + } + + JsonArray stylesheets; + stylesheets.must_append("stylesheet"sv); + stylesheets.must_append(move(sheets)); + + JsonArray array; + array.must_append(move(stylesheets)); + + response.set("type"sv, "resources-available-array"sv); + response.set("array"sv, move(array)); + + style_sheets_actor->set_style_sheets(move(style_sheets)); +} + void FrameActor::console_message_available(i32 message_index) { if (message_index <= m_highest_received_message_index) { diff --git a/Libraries/LibDevTools/Actors/FrameActor.h b/Libraries/LibDevTools/Actors/FrameActor.h index 3f808ea3408..1c53415c136 100644 --- a/Libraries/LibDevTools/Actors/FrameActor.h +++ b/Libraries/LibDevTools/Actors/FrameActor.h @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace DevTools { @@ -18,7 +19,7 @@ class FrameActor final : public Actor { public: static constexpr auto base_name = "frame"sv; - static NonnullRefPtr create(DevToolsServer&, String name, WeakPtr, WeakPtr, WeakPtr, WeakPtr, WeakPtr); + static NonnullRefPtr create(DevToolsServer&, String name, WeakPtr, WeakPtr, WeakPtr, WeakPtr, WeakPtr, WeakPtr); virtual ~FrameActor() override; void send_frame_update_message(); @@ -26,7 +27,9 @@ public: JsonObject serialize_target() const; private: - FrameActor(DevToolsServer&, String name, WeakPtr, WeakPtr, WeakPtr, WeakPtr, WeakPtr); + FrameActor(DevToolsServer&, String name, WeakPtr, WeakPtr, WeakPtr, WeakPtr, WeakPtr, WeakPtr); + + void style_sheets_available(JsonObject& response, Vector style_sheets); virtual void handle_message(Message const&) override; @@ -39,6 +42,7 @@ private: WeakPtr m_css_properties; WeakPtr m_console; WeakPtr m_inspector; + WeakPtr m_style_sheets; WeakPtr m_thread; i32 m_highest_notified_message_index { -1 }; diff --git a/Libraries/LibDevTools/Actors/StyleSheetsActor.cpp b/Libraries/LibDevTools/Actors/StyleSheetsActor.cpp new file mode 100644 index 00000000000..5630f287244 --- /dev/null +++ b/Libraries/LibDevTools/Actors/StyleSheetsActor.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace DevTools { + +NonnullRefPtr StyleSheetsActor::create(DevToolsServer& devtools, String name, WeakPtr tab) +{ + return adopt_ref(*new StyleSheetsActor(devtools, move(name), move(tab))); +} + +StyleSheetsActor::StyleSheetsActor(DevToolsServer& devtools, String name, WeakPtr tab) + : Actor(devtools, move(name)) + , m_tab(move(tab)) +{ + if (auto tab = m_tab.strong_ref()) { + devtools.delegate().listen_for_style_sheet_sources( + tab->description(), + [weak_self = make_weak_ptr()](Web::CSS::StyleSheetIdentifier const& style_sheet, String source) { + if (auto self = weak_self.strong_ref()) + self->style_sheet_source_received(style_sheet, move(source)); + }); + } +} + +StyleSheetsActor::~StyleSheetsActor() +{ + if (auto tab = m_tab.strong_ref()) + devtools().delegate().stop_listening_for_style_sheet_sources(tab->description()); +} + +void StyleSheetsActor::handle_message(Message const& message) +{ + if (message.type == "getText"sv) { + auto resource_id = get_required_parameter(message, "resourceId"sv); + if (!resource_id.has_value()) + return; + + auto index = resource_id->bytes_as_string_view().find_last_split_view(':').to_number(); + if (!index.has_value() || *index >= m_style_sheets.size()) { + send_unknown_actor_error(message, *resource_id); + return; + } + + if (auto tab = m_tab.strong_ref()) { + devtools().delegate().retrieve_style_sheet_source(tab->description(), m_style_sheets[*index]); + m_pending_style_sheet_source_requests.set(*index, { .id = message.id }); + } + + return; + } + + send_unrecognized_packet_type_error(message); +} + +void StyleSheetsActor::set_style_sheets(Vector style_sheets) +{ + m_style_sheets = move(style_sheets); +} + +void StyleSheetsActor::style_sheet_source_received(Web::CSS::StyleSheetIdentifier const& style_sheet, String source) +{ + auto index = m_style_sheets.find_first_index_if([&](auto const& candidate) { + return candidate.type == style_sheet.type && candidate.url == style_sheet.url; + }); + if (!index.has_value()) + return; + + auto pending_message = m_pending_style_sheet_source_requests.take(*index); + if (!pending_message.has_value()) + return; + + // FIXME: Support the `longString` message type so that we don't have to send the entire style sheet + // source at once for large sheets. + JsonObject response; + response.set("text"sv, move(source)); + send_response(*pending_message, move(response)); +} + +} diff --git a/Libraries/LibDevTools/Actors/StyleSheetsActor.h b/Libraries/LibDevTools/Actors/StyleSheetsActor.h new file mode 100644 index 00000000000..206e63ea44b --- /dev/null +++ b/Libraries/LibDevTools/Actors/StyleSheetsActor.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace DevTools { + +class StyleSheetsActor final : public Actor { +public: + static constexpr auto base_name = "style-sheets"sv; + + static NonnullRefPtr create(DevToolsServer&, String name, WeakPtr); + virtual ~StyleSheetsActor() override; + + void set_style_sheets(Vector); + +private: + StyleSheetsActor(DevToolsServer&, String name, WeakPtr); + + virtual void handle_message(Message const&) override; + + void style_sheet_source_received(Web::CSS::StyleSheetIdentifier const&, String source); + + WeakPtr m_tab; + + Vector m_style_sheets; + HashMap m_pending_style_sheet_source_requests; +}; + +} diff --git a/Libraries/LibDevTools/Actors/WatcherActor.cpp b/Libraries/LibDevTools/Actors/WatcherActor.cpp index 371787c98fb..3104ae00eea 100644 --- a/Libraries/LibDevTools/Actors/WatcherActor.cpp +++ b/Libraries/LibDevTools/Actors/WatcherActor.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -92,9 +93,10 @@ void WatcherActor::handle_message(Message const& message) auto& css_properties = devtools().register_actor(); auto& console = devtools().register_actor(m_tab); auto& inspector = devtools().register_actor(m_tab); + auto& style_sheets = devtools().register_actor(m_tab); auto& thread = devtools().register_actor(); - auto& target = devtools().register_actor(m_tab, css_properties, console, inspector, thread); + auto& target = devtools().register_actor(m_tab, css_properties, console, inspector, style_sheets, thread); m_target = target; response.set("type"sv, "target-available-form"sv); diff --git a/Libraries/LibDevTools/CMakeLists.txt b/Libraries/LibDevTools/CMakeLists.txt index e55a1c287de..e0151ea6570 100644 --- a/Libraries/LibDevTools/CMakeLists.txt +++ b/Libraries/LibDevTools/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES Actors/PreferenceActor.cpp Actors/ProcessActor.cpp Actors/RootActor.cpp + Actors/StyleSheetsActor.cpp Actors/TabActor.cpp Actors/TargetConfigurationActor.cpp Actors/ThreadActor.cpp diff --git a/Libraries/LibDevTools/DevToolsDelegate.h b/Libraries/LibDevTools/DevToolsDelegate.h index 6c84239d08d..a050087bc63 100644 --- a/Libraries/LibDevTools/DevToolsDelegate.h +++ b/Libraries/LibDevTools/DevToolsDelegate.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,13 @@ public: virtual void clone_dom_node(TabDescription const&, Web::UniqueNodeID, OnDOMNodeEditComplete) const { } virtual void remove_dom_node(TabDescription const&, Web::UniqueNodeID, OnDOMNodeEditComplete) const { } + using OnStyleSheetsReceived = Function>)>; + using OnStyleSheetSourceReceived = Function; + virtual void retrieve_style_sheets(TabDescription const&, OnStyleSheetsReceived) const { } + virtual void retrieve_style_sheet_source(TabDescription const&, Web::CSS::StyleSheetIdentifier const&) const { } + virtual void listen_for_style_sheet_sources(TabDescription const&, OnStyleSheetSourceReceived) const { } + virtual void stop_listening_for_style_sheet_sources(TabDescription const&) const { } + using OnScriptEvaluationComplete = Function)>; virtual void evaluate_javascript(TabDescription const&, String const&, OnScriptEvaluationComplete) const { } diff --git a/Libraries/LibDevTools/Forward.h b/Libraries/LibDevTools/Forward.h index 3754322021e..d77f025ea2d 100644 --- a/Libraries/LibDevTools/Forward.h +++ b/Libraries/LibDevTools/Forward.h @@ -24,6 +24,7 @@ class PageStyleActor; class PreferenceActor; class ProcessActor; class RootActor; +class StyleSheetsActor; class TabActor; class TargetConfigurationActor; class ThreadActor; diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index 05f49dfdfbf..e301be9d346 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -603,6 +603,51 @@ void Application::remove_dom_node(DevTools::TabDescription const& description, W }); } +void Application::retrieve_style_sheets(DevTools::TabDescription const& description, OnStyleSheetsReceived on_complete) const +{ + auto view = ViewImplementation::find_view_by_id(description.id); + if (!view.has_value()) { + on_complete(Error::from_string_literal("Unable to locate tab")); + return; + } + + view->on_received_style_sheet_list = [&view = *view, on_complete = move(on_complete)](Vector style_sheets) { + view.on_received_style_sheet_list = nullptr; + on_complete(move(style_sheets)); + }; + + view->list_style_sheets(); +} + +void Application::retrieve_style_sheet_source(DevTools::TabDescription const& description, Web::CSS::StyleSheetIdentifier const& style_sheet) const +{ + auto view = ViewImplementation::find_view_by_id(description.id); + if (!view.has_value()) + return; + + view->request_style_sheet_source(style_sheet); +} + +void Application::listen_for_style_sheet_sources(DevTools::TabDescription const& description, OnStyleSheetSourceReceived on_style_sheet_source_received) const +{ + auto view = ViewImplementation::find_view_by_id(description.id); + if (!view.has_value()) + return; + + view->on_received_style_sheet_source = [&view = *view, on_style_sheet_source_received = move(on_style_sheet_source_received)](auto const& style_sheet, auto const&, auto const& source) { + on_style_sheet_source_received(style_sheet, source); + }; +} + +void Application::stop_listening_for_style_sheet_sources(DevTools::TabDescription const& description) const +{ + auto view = ViewImplementation::find_view_by_id(description.id); + if (!view.has_value()) + return; + + view->on_received_style_sheet_source = nullptr; +} + void Application::evaluate_javascript(DevTools::TabDescription const& description, String const& script, OnScriptEvaluationComplete on_complete) const { auto view = ViewImplementation::find_view_by_id(description.id); diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index 089aa77ecd2..83c77847b88 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -111,6 +111,10 @@ private: virtual void insert_dom_node_before(DevTools::TabDescription const&, Web::UniqueNodeID, Web::UniqueNodeID, Optional, OnDOMNodeEditComplete) const override; virtual void clone_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID, OnDOMNodeEditComplete) const override; virtual void remove_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID, OnDOMNodeEditComplete) const override; + virtual void retrieve_style_sheets(DevTools::TabDescription const&, OnStyleSheetsReceived) const override; + virtual void retrieve_style_sheet_source(DevTools::TabDescription const&, Web::CSS::StyleSheetIdentifier const&) const override; + virtual void listen_for_style_sheet_sources(DevTools::TabDescription const&, OnStyleSheetSourceReceived) const override; + virtual void stop_listening_for_style_sheet_sources(DevTools::TabDescription const&) const override; virtual void evaluate_javascript(DevTools::TabDescription const&, String const&, OnScriptEvaluationComplete) const override; virtual void listen_for_console_messages(DevTools::TabDescription const&, OnConsoleMessageAvailable, OnReceivedConsoleMessages) const override; virtual void stop_listening_for_console_messages(DevTools::TabDescription const&) const override;