mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 20:29:18 +00:00
LibDevTools: Implement enough of the protocol to inspect tabs
There is a lot needed all at once to actually inspect a tab's DOM tree. It begins with requesting a "watcher" from a TabActor. It seems there can be many types of watchers, but here we implement the "frame" watcher only. The watcher creates an "inspector", which in turn creates a "walker", which is the actor ultimately responsible for serializing and inspecting the DOM tree. In between all that, the DevTools client will send a handful of other informational requests. If we do not reply to these, the client will not move forward with the walker. For example, the CSSPropertiesActor will be asked for a list of all known CSS properties.
This commit is contained in:
parent
b974e91731
commit
5ed91dc915
Notes:
github-actions[bot]
2025-02-19 13:47:12 +00:00
Author: https://github.com/trflynn89
Commit: 5ed91dc915
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3589
Reviewed-by: https://github.com/ADKaster
25 changed files with 1278 additions and 0 deletions
58
Libraries/LibDevTools/Actors/CSSPropertiesActor.cpp
Normal file
58
Libraries/LibDevTools/Actors/CSSPropertiesActor.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/JsonArray.h>
|
||||||
|
#include <AK/JsonObject.h>
|
||||||
|
#include <LibDevTools/Actors/CSSPropertiesActor.h>
|
||||||
|
#include <LibDevTools/DevToolsDelegate.h>
|
||||||
|
#include <LibDevTools/DevToolsServer.h>
|
||||||
|
|
||||||
|
namespace DevTools {
|
||||||
|
|
||||||
|
NonnullRefPtr<CSSPropertiesActor> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
Libraries/LibDevTools/Actors/CSSPropertiesActor.h
Normal file
33
Libraries/LibDevTools/Actors/CSSPropertiesActor.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/ByteString.h>
|
||||||
|
#include <AK/NonnullRefPtr.h>
|
||||||
|
#include <LibDevTools/Actor.h>
|
||||||
|
|
||||||
|
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<CSSPropertiesActor> create(DevToolsServer&, ByteString name);
|
||||||
|
virtual ~CSSPropertiesActor() override;
|
||||||
|
|
||||||
|
virtual void handle_message(StringView type, JsonObject const&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CSSPropertiesActor(DevToolsServer&, ByteString name);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
98
Libraries/LibDevTools/Actors/FrameActor.cpp
Normal file
98
Libraries/LibDevTools/Actors/FrameActor.cpp
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/JsonArray.h>
|
||||||
|
#include <AK/JsonObject.h>
|
||||||
|
#include <LibDevTools/Actors/CSSPropertiesActor.h>
|
||||||
|
#include <LibDevTools/Actors/FrameActor.h>
|
||||||
|
#include <LibDevTools/Actors/InspectorActor.h>
|
||||||
|
#include <LibDevTools/Actors/TabActor.h>
|
||||||
|
#include <LibDevTools/Actors/ThreadActor.h>
|
||||||
|
|
||||||
|
namespace DevTools {
|
||||||
|
|
||||||
|
NonnullRefPtr<FrameActor> FrameActor::create(DevToolsServer& devtools, ByteString name, WeakPtr<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<InspectorActor> inspector, WeakPtr<ThreadActor> 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<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<InspectorActor> inspector, WeakPtr<ThreadActor> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
36
Libraries/LibDevTools/Actors/FrameActor.h
Normal file
36
Libraries/LibDevTools/Actors/FrameActor.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
class FrameActor final : public Actor {
|
||||||
|
public:
|
||||||
|
static constexpr auto base_name = "frame"sv;
|
||||||
|
|
||||||
|
static NonnullRefPtr<FrameActor> create(DevToolsServer&, ByteString name, WeakPtr<TabActor>, WeakPtr<CSSPropertiesActor>, WeakPtr<InspectorActor>, WeakPtr<ThreadActor>);
|
||||||
|
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<TabActor>, WeakPtr<CSSPropertiesActor>, WeakPtr<InspectorActor>, WeakPtr<ThreadActor>);
|
||||||
|
|
||||||
|
WeakPtr<TabActor> m_tab;
|
||||||
|
|
||||||
|
WeakPtr<CSSPropertiesActor> m_css_properties;
|
||||||
|
WeakPtr<InspectorActor> m_inspector;
|
||||||
|
WeakPtr<ThreadActor> m_thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
50
Libraries/LibDevTools/Actors/HighlighterActor.cpp
Normal file
50
Libraries/LibDevTools/Actors/HighlighterActor.cpp
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/JsonObject.h>
|
||||||
|
#include <LibDevTools/Actors/HighlighterActor.h>
|
||||||
|
|
||||||
|
namespace DevTools {
|
||||||
|
|
||||||
|
NonnullRefPtr<HighlighterActor> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
Libraries/LibDevTools/Actors/HighlighterActor.h
Normal file
28
Libraries/LibDevTools/Actors/HighlighterActor.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
class HighlighterActor final : public Actor {
|
||||||
|
public:
|
||||||
|
static constexpr auto base_name = "highlighter"sv;
|
||||||
|
|
||||||
|
static NonnullRefPtr<HighlighterActor> 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
95
Libraries/LibDevTools/Actors/InspectorActor.cpp
Normal file
95
Libraries/LibDevTools/Actors/InspectorActor.cpp
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/Debug.h>
|
||||||
|
#include <AK/JsonObject.h>
|
||||||
|
#include <LibDevTools/Actors/HighlighterActor.h>
|
||||||
|
#include <LibDevTools/Actors/InspectorActor.h>
|
||||||
|
#include <LibDevTools/Actors/PageStyleActor.h>
|
||||||
|
#include <LibDevTools/Actors/TabActor.h>
|
||||||
|
#include <LibDevTools/Actors/WalkerActor.h>
|
||||||
|
#include <LibDevTools/DevToolsDelegate.h>
|
||||||
|
#include <LibDevTools/DevToolsServer.h>
|
||||||
|
|
||||||
|
namespace DevTools {
|
||||||
|
|
||||||
|
NonnullRefPtr<InspectorActor> InspectorActor::create(DevToolsServer& devtools, ByteString name, WeakPtr<TabActor> tab)
|
||||||
|
{
|
||||||
|
return adopt_ref(*new InspectorActor(devtools, move(name), move(tab)));
|
||||||
|
}
|
||||||
|
|
||||||
|
InspectorActor::InspectorActor(DevToolsServer& devtools, ByteString name, WeakPtr<TabActor> 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<PageStyleActor>();
|
||||||
|
|
||||||
|
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<HighlighterActor>();
|
||||||
|
|
||||||
|
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<InspectorActor>(), block_token = move(block_token)](ErrorOr<JsonValue> 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<WalkerActor>(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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
Libraries/LibDevTools/Actors/InspectorActor.h
Normal file
33
Libraries/LibDevTools/Actors/InspectorActor.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
class InspectorActor final : public Actor {
|
||||||
|
public:
|
||||||
|
static constexpr auto base_name = "inspector"sv;
|
||||||
|
|
||||||
|
static NonnullRefPtr<InspectorActor> create(DevToolsServer&, ByteString name, WeakPtr<TabActor>);
|
||||||
|
virtual ~InspectorActor() override;
|
||||||
|
|
||||||
|
virtual void handle_message(StringView type, JsonObject const&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
InspectorActor(DevToolsServer&, ByteString name, WeakPtr<TabActor>);
|
||||||
|
|
||||||
|
void received_dom_tree(JsonObject, BlockToken);
|
||||||
|
|
||||||
|
WeakPtr<TabActor> m_tab;
|
||||||
|
WeakPtr<PageStyleActor> m_page_style;
|
||||||
|
WeakPtr<HighlighterActor> m_highlighter;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
43
Libraries/LibDevTools/Actors/PageStyleActor.cpp
Normal file
43
Libraries/LibDevTools/Actors/PageStyleActor.cpp
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/JsonObject.h>
|
||||||
|
#include <LibDevTools/Actors/PageStyleActor.h>
|
||||||
|
|
||||||
|
namespace DevTools {
|
||||||
|
|
||||||
|
NonnullRefPtr<PageStyleActor> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
Libraries/LibDevTools/Actors/PageStyleActor.h
Normal file
28
Libraries/LibDevTools/Actors/PageStyleActor.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
class PageStyleActor final : public Actor {
|
||||||
|
public:
|
||||||
|
static constexpr auto base_name = "page-style"sv;
|
||||||
|
|
||||||
|
static NonnullRefPtr<PageStyleActor> 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include <AK/JsonObject.h>
|
#include <AK/JsonObject.h>
|
||||||
#include <LibDevTools/Actors/TabActor.h>
|
#include <LibDevTools/Actors/TabActor.h>
|
||||||
|
#include <LibDevTools/Actors/WatcherActor.h>
|
||||||
#include <LibDevTools/DevToolsServer.h>
|
#include <LibDevTools/DevToolsServer.h>
|
||||||
|
|
||||||
namespace DevTools {
|
namespace DevTools {
|
||||||
|
@ -36,6 +37,16 @@ void TabActor::handle_message(StringView type, JsonObject const&)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type == "getWatcher"sv) {
|
||||||
|
if (!m_watcher)
|
||||||
|
m_watcher = devtools().register_actor<WatcherActor>(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);
|
send_unrecognized_packet_type_error(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ private:
|
||||||
TabActor(DevToolsServer&, ByteString name, TabDescription);
|
TabActor(DevToolsServer&, ByteString name, TabDescription);
|
||||||
|
|
||||||
TabDescription m_description;
|
TabDescription m_description;
|
||||||
|
WeakPtr<WatcherActor> m_watcher;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
76
Libraries/LibDevTools/Actors/TargetConfigurationActor.cpp
Normal file
76
Libraries/LibDevTools/Actors/TargetConfigurationActor.cpp
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/JsonArray.h>
|
||||||
|
#include <AK/JsonObject.h>
|
||||||
|
#include <LibDevTools/Actors/TargetConfigurationActor.h>
|
||||||
|
|
||||||
|
namespace DevTools {
|
||||||
|
|
||||||
|
NonnullRefPtr<TargetConfigurationActor> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
Libraries/LibDevTools/Actors/TargetConfigurationActor.h
Normal file
29
Libraries/LibDevTools/Actors/TargetConfigurationActor.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
class TargetConfigurationActor final : public Actor {
|
||||||
|
public:
|
||||||
|
static constexpr auto base_name = "target-configuration"sv;
|
||||||
|
|
||||||
|
static NonnullRefPtr<TargetConfigurationActor> 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
28
Libraries/LibDevTools/Actors/ThreadActor.cpp
Normal file
28
Libraries/LibDevTools/Actors/ThreadActor.cpp
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibDevTools/Actors/ThreadActor.h>
|
||||||
|
|
||||||
|
namespace DevTools {
|
||||||
|
|
||||||
|
NonnullRefPtr<ThreadActor> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
27
Libraries/LibDevTools/Actors/ThreadActor.h
Normal file
27
Libraries/LibDevTools/Actors/ThreadActor.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
class ThreadActor final : public Actor {
|
||||||
|
public:
|
||||||
|
static constexpr auto base_name = "thread"sv;
|
||||||
|
|
||||||
|
static NonnullRefPtr<ThreadActor> create(DevToolsServer&, ByteString name);
|
||||||
|
virtual ~ThreadActor() override;
|
||||||
|
|
||||||
|
virtual void handle_message(StringView type, JsonObject const&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ThreadActor(DevToolsServer&, ByteString name);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
52
Libraries/LibDevTools/Actors/ThreadConfigurationActor.cpp
Normal file
52
Libraries/LibDevTools/Actors/ThreadConfigurationActor.cpp
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/JsonArray.h>
|
||||||
|
#include <AK/JsonObject.h>
|
||||||
|
#include <LibDevTools/Actors/ThreadConfigurationActor.h>
|
||||||
|
|
||||||
|
namespace DevTools {
|
||||||
|
|
||||||
|
NonnullRefPtr<ThreadConfigurationActor> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
Libraries/LibDevTools/Actors/ThreadConfigurationActor.h
Normal file
29
Libraries/LibDevTools/Actors/ThreadConfigurationActor.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
class ThreadConfigurationActor final : public Actor {
|
||||||
|
public:
|
||||||
|
static constexpr auto base_name = "thread-configuration"sv;
|
||||||
|
|
||||||
|
static NonnullRefPtr<ThreadConfigurationActor> 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
275
Libraries/LibDevTools/Actors/WalkerActor.cpp
Normal file
275
Libraries/LibDevTools/Actors/WalkerActor.cpp
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/JsonArray.h>
|
||||||
|
#include <LibDevTools/Actors/TabActor.h>
|
||||||
|
#include <LibDevTools/Actors/WalkerActor.h>
|
||||||
|
#include <LibWeb/DOM/NodeType.h>
|
||||||
|
|
||||||
|
namespace DevTools {
|
||||||
|
|
||||||
|
NonnullRefPtr<WalkerActor> WalkerActor::create(DevToolsServer& devtools, ByteString name, WeakPtr<TabActor> tab, JsonObject dom_tree)
|
||||||
|
{
|
||||||
|
return adopt_ref(*new WalkerActor(devtools, move(name), move(tab), move(dom_tree)));
|
||||||
|
}
|
||||||
|
|
||||||
|
WalkerActor::WalkerActor(DevToolsServer& devtools, ByteString name, WeakPtr<TabActor> 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<JsonObject const&> 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
45
Libraries/LibDevTools/Actors/WalkerActor.h
Normal file
45
Libraries/LibDevTools/Actors/WalkerActor.h
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/HashMap.h>
|
||||||
|
#include <AK/JsonObject.h>
|
||||||
|
#include <AK/NonnullRefPtr.h>
|
||||||
|
#include <AK/Optional.h>
|
||||||
|
#include <LibDevTools/Actor.h>
|
||||||
|
|
||||||
|
namespace DevTools {
|
||||||
|
|
||||||
|
class WalkerActor final : public Actor {
|
||||||
|
public:
|
||||||
|
static constexpr auto base_name = "walker"sv;
|
||||||
|
|
||||||
|
static NonnullRefPtr<WalkerActor> create(DevToolsServer&, ByteString name, WeakPtr<TabActor>, 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<TabActor>, JsonObject dom_tree);
|
||||||
|
|
||||||
|
JsonValue serialize_node(JsonObject const&) const;
|
||||||
|
Optional<JsonObject const&> find_node_by_selector(JsonObject const& node, StringView selector);
|
||||||
|
|
||||||
|
void populate_dom_tree_cache(JsonObject& node, JsonObject const* parent = nullptr);
|
||||||
|
|
||||||
|
WeakPtr<TabActor> m_tab;
|
||||||
|
JsonObject m_dom_tree;
|
||||||
|
|
||||||
|
HashMap<JsonObject const*, JsonObject const*> m_dom_node_to_parent_map;
|
||||||
|
HashMap<ByteString, JsonObject const*> m_actor_to_dom_node_map;
|
||||||
|
size_t m_dom_node_count { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
140
Libraries/LibDevTools/Actors/WatcherActor.cpp
Normal file
140
Libraries/LibDevTools/Actors/WatcherActor.cpp
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/JsonObject.h>
|
||||||
|
#include <LibCore/EventLoop.h>
|
||||||
|
#include <LibDevTools/Actors/CSSPropertiesActor.h>
|
||||||
|
#include <LibDevTools/Actors/FrameActor.h>
|
||||||
|
#include <LibDevTools/Actors/InspectorActor.h>
|
||||||
|
#include <LibDevTools/Actors/TabActor.h>
|
||||||
|
#include <LibDevTools/Actors/TargetConfigurationActor.h>
|
||||||
|
#include <LibDevTools/Actors/ThreadActor.h>
|
||||||
|
#include <LibDevTools/Actors/ThreadConfigurationActor.h>
|
||||||
|
#include <LibDevTools/Actors/WatcherActor.h>
|
||||||
|
#include <LibDevTools/DevToolsServer.h>
|
||||||
|
|
||||||
|
namespace DevTools {
|
||||||
|
|
||||||
|
NonnullRefPtr<WatcherActor> WatcherActor::create(DevToolsServer& devtools, ByteString name, WeakPtr<TabActor> tab)
|
||||||
|
{
|
||||||
|
return adopt_ref(*new WatcherActor(devtools, move(name), move(tab)));
|
||||||
|
}
|
||||||
|
|
||||||
|
WatcherActor::WatcherActor(DevToolsServer& devtools, ByteString name, WeakPtr<TabActor> 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<u64>("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<TargetConfigurationActor>();
|
||||||
|
|
||||||
|
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<ThreadConfigurationActor>();
|
||||||
|
|
||||||
|
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<CSSPropertiesActor>();
|
||||||
|
auto& inspector = devtools().register_actor<InspectorActor>(m_tab);
|
||||||
|
auto& thread = devtools().register_actor<ThreadActor>();
|
||||||
|
|
||||||
|
auto& target = devtools().register_actor<FrameActor>(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
34
Libraries/LibDevTools/Actors/WatcherActor.h
Normal file
34
Libraries/LibDevTools/Actors/WatcherActor.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
class WatcherActor final : public Actor {
|
||||||
|
public:
|
||||||
|
static constexpr auto base_name = "watcher"sv;
|
||||||
|
|
||||||
|
static NonnullRefPtr<WatcherActor> create(DevToolsServer&, ByteString name, WeakPtr<TabActor>);
|
||||||
|
virtual ~WatcherActor() override;
|
||||||
|
|
||||||
|
virtual void handle_message(StringView type, JsonObject const&) override;
|
||||||
|
|
||||||
|
JsonObject serialize_description() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
WatcherActor(DevToolsServer&, ByteString name, WeakPtr<TabActor>);
|
||||||
|
|
||||||
|
WeakPtr<TabActor> m_tab;
|
||||||
|
WeakPtr<Actor> m_target;
|
||||||
|
WeakPtr<TargetConfigurationActor> m_target_configuration;
|
||||||
|
WeakPtr<ThreadConfigurationActor> m_thread_configuration;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -1,10 +1,20 @@
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
Actor.cpp
|
Actor.cpp
|
||||||
|
Actors/CSSPropertiesActor.cpp
|
||||||
Actors/DeviceActor.cpp
|
Actors/DeviceActor.cpp
|
||||||
|
Actors/FrameActor.cpp
|
||||||
|
Actors/HighlighterActor.cpp
|
||||||
|
Actors/InspectorActor.cpp
|
||||||
|
Actors/PageStyleActor.cpp
|
||||||
Actors/PreferenceActor.cpp
|
Actors/PreferenceActor.cpp
|
||||||
Actors/ProcessActor.cpp
|
Actors/ProcessActor.cpp
|
||||||
Actors/RootActor.cpp
|
Actors/RootActor.cpp
|
||||||
Actors/TabActor.cpp
|
Actors/TabActor.cpp
|
||||||
|
Actors/TargetConfigurationActor.cpp
|
||||||
|
Actors/ThreadActor.cpp
|
||||||
|
Actors/ThreadConfigurationActor.cpp
|
||||||
|
Actors/WalkerActor.cpp
|
||||||
|
Actors/WatcherActor.cpp
|
||||||
Connection.cpp
|
Connection.cpp
|
||||||
DevToolsServer.cpp
|
DevToolsServer.cpp
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,11 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Error.h>
|
||||||
|
#include <AK/Function.h>
|
||||||
|
#include <AK/JsonValue.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
#include <LibDevTools/Actors/CSSPropertiesActor.h>
|
||||||
#include <LibDevTools/Actors/TabActor.h>
|
#include <LibDevTools/Actors/TabActor.h>
|
||||||
#include <LibDevTools/Forward.h>
|
#include <LibDevTools/Forward.h>
|
||||||
|
|
||||||
|
@ -17,6 +21,10 @@ public:
|
||||||
virtual ~DevToolsDelegate() = default;
|
virtual ~DevToolsDelegate() = default;
|
||||||
|
|
||||||
virtual Vector<TabDescription> tab_list() const { return {}; }
|
virtual Vector<TabDescription> tab_list() const { return {}; }
|
||||||
|
virtual Vector<CSSProperty> css_property_list() const { return {}; }
|
||||||
|
|
||||||
|
using OnTabInspectionComplete = Function<void(ErrorOr<JsonValue>)>;
|
||||||
|
virtual void inspect_tab(TabDescription const&, OnTabInspectionComplete) const { }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,25 @@ namespace DevTools {
|
||||||
|
|
||||||
class Actor;
|
class Actor;
|
||||||
class Connection;
|
class Connection;
|
||||||
|
class CSSPropertiesActor;
|
||||||
class DeviceActor;
|
class DeviceActor;
|
||||||
class DevToolsDelegate;
|
class DevToolsDelegate;
|
||||||
class DevToolsServer;
|
class DevToolsServer;
|
||||||
|
class FrameActor;
|
||||||
|
class HighlighterActor;
|
||||||
|
class InspectorActor;
|
||||||
|
class PageStyleActor;
|
||||||
class PreferenceActor;
|
class PreferenceActor;
|
||||||
class ProcessActor;
|
class ProcessActor;
|
||||||
class RootActor;
|
class RootActor;
|
||||||
class TabActor;
|
class TabActor;
|
||||||
|
class TargetConfigurationActor;
|
||||||
|
class ThreadActor;
|
||||||
|
class ThreadConfigurationActor;
|
||||||
|
class WalkerActor;
|
||||||
|
class WatcherActor;
|
||||||
|
|
||||||
|
struct CSSProperty;
|
||||||
struct ProcessDescription;
|
struct ProcessDescription;
|
||||||
struct TabDescription;
|
struct TabDescription;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue