LibWeb+LibDevTools: Report applied style rules to Firefox
Collect the style rules that apply to an inspected element and expose them through the existing DOM node inspection path. This gives Firefox's Rules panel real rule forms instead of the previous empty getApplied response.
This commit is contained in:
parent
121105eb83
commit
52e1d30404
16 changed files with 728 additions and 32 deletions
|
|
@ -9,6 +9,7 @@
|
|||
#include <AK/JsonValue.h>
|
||||
#include <LibDevTools/Actors/InspectorActor.h>
|
||||
#include <LibDevTools/Actors/PageStyleActor.h>
|
||||
#include <LibDevTools/Actors/StyleRuleActor.h>
|
||||
#include <LibDevTools/Actors/TabActor.h>
|
||||
#include <LibDevTools/Actors/WalkerActor.h>
|
||||
#include <LibDevTools/DevToolsDelegate.h>
|
||||
|
|
@ -111,6 +112,42 @@ static void received_fonts(JsonObject& response, JsonValue const& fonts)
|
|||
response.set("fontFaces"sv, move(font_faces));
|
||||
}
|
||||
|
||||
void PageStyleActor::received_applied_style_rules(JsonObject& response, JsonValue const& applied_style_rules)
|
||||
{
|
||||
JsonArray entries;
|
||||
clear_style_rule_actors();
|
||||
|
||||
if (applied_style_rules.is_array()) {
|
||||
applied_style_rules.as_array().for_each([&](JsonValue const& value) {
|
||||
if (!value.is_object())
|
||||
return;
|
||||
|
||||
auto entry = value.as_object();
|
||||
auto rule = entry.get_object("rule"sv);
|
||||
if (!rule.has_value())
|
||||
return;
|
||||
|
||||
auto& style_rule_actor = devtools().register_actor<StyleRuleActor>(*rule);
|
||||
m_style_rule_actors.append(style_rule_actor.name());
|
||||
entry.set("rule"sv, style_rule_actor.serialize_rule());
|
||||
|
||||
if (auto inherited_node_id = entry.get_integer<Web::UniqueNodeID::Type>("inheritedNodeId"sv); inherited_node_id.has_value()) {
|
||||
JsonValue inherited { JsonValue {} };
|
||||
if (auto walker = InspectorActor::walker_for(m_inspector)) {
|
||||
if (auto inherited_node_actor = walker->node_actor_name_for(Web::UniqueNodeID { *inherited_node_id }); inherited_node_actor.has_value())
|
||||
inherited = *inherited_node_actor;
|
||||
}
|
||||
entry.set("inherited"sv, move(inherited));
|
||||
entry.remove("inheritedNodeId"sv);
|
||||
}
|
||||
|
||||
entries.must_append(move(entry));
|
||||
});
|
||||
}
|
||||
|
||||
response.set("entries"sv, move(entries));
|
||||
}
|
||||
|
||||
NonnullRefPtr<PageStyleActor> PageStyleActor::create(DevToolsServer& devtools, String name, WeakPtr<InspectorActor> inspector)
|
||||
{
|
||||
return adopt_ref(*new PageStyleActor(devtools, move(name), move(inspector)));
|
||||
|
|
@ -130,10 +167,19 @@ PageStyleActor::PageStyleActor(DevToolsServer& devtools, String name, WeakPtr<In
|
|||
|
||||
PageStyleActor::~PageStyleActor()
|
||||
{
|
||||
clear_style_rule_actors();
|
||||
|
||||
if (auto tab = InspectorActor::tab_for(m_inspector))
|
||||
devtools().delegate().stop_listening_for_dom_properties(tab->description());
|
||||
}
|
||||
|
||||
void PageStyleActor::clear_style_rule_actors()
|
||||
{
|
||||
for (auto const& actor : m_style_rule_actors)
|
||||
devtools().unregister_actor(actor);
|
||||
m_style_rule_actors.clear();
|
||||
}
|
||||
|
||||
void PageStyleActor::handle_message(Message const& message)
|
||||
{
|
||||
JsonObject response;
|
||||
|
|
@ -145,10 +191,7 @@ void PageStyleActor::handle_message(Message const& message)
|
|||
}
|
||||
|
||||
if (message.type == "getApplied"sv) {
|
||||
// FIXME: This provides information to the "styles" pane in the inspector tab, which allows toggling and editing
|
||||
// styles live. We do not yet support figuring out the list of styles that apply to a specific node.
|
||||
response.set("entries"sv, JsonArray {});
|
||||
send_response(message, move(response));
|
||||
inspect_dom_node(message, WebView::DOMNodeProperties::Type::AppliedStyleRules);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +245,19 @@ void PageStyleActor::inspect_dom_node(Message const& message, WebView::DOMNodePr
|
|||
return;
|
||||
}
|
||||
|
||||
devtools().delegate().inspect_dom_node(dom_node->tab->description(), property_type, dom_node->identifier.id, dom_node->identifier.pseudo_element);
|
||||
JsonObject options;
|
||||
if (property_type == WebView::DOMNodeProperties::Type::AppliedStyleRules) {
|
||||
if (auto inherited = message.data.get_bool("inherited"sv); inherited.has_value())
|
||||
options.set("inherited"sv, *inherited);
|
||||
if (auto matched_selectors = message.data.get_bool("matchedSelectors"sv); matched_selectors.has_value())
|
||||
options.set("matchedSelectors"sv, *matched_selectors);
|
||||
if (auto skip_pseudo = message.data.get_bool("skipPseudo"sv); skip_pseudo.has_value())
|
||||
options.set("skipPseudo"sv, *skip_pseudo);
|
||||
if (auto filter = message.data.get_string("filter"sv); filter.has_value())
|
||||
options.set("filter"sv, *filter);
|
||||
}
|
||||
|
||||
devtools().delegate().inspect_dom_node(dom_node->tab->description(), property_type, dom_node->identifier.id, dom_node->identifier.pseudo_element, move(options));
|
||||
m_pending_inspect_requests.append({ .id = message.id });
|
||||
}
|
||||
|
||||
|
|
@ -214,6 +269,9 @@ void PageStyleActor::received_dom_node_properties(WebView::DOMNodeProperties con
|
|||
JsonObject response;
|
||||
|
||||
switch (properties.type) {
|
||||
case WebView::DOMNodeProperties::Type::AppliedStyleRules:
|
||||
received_applied_style_rules(response, properties.properties);
|
||||
break;
|
||||
case WebView::DOMNodeProperties::Type::ComputedStyle:
|
||||
received_computed_style(response, properties.properties);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -28,11 +28,14 @@ private:
|
|||
virtual void handle_message(Message const&) override;
|
||||
|
||||
void inspect_dom_node(Message const&, WebView::DOMNodeProperties::Type);
|
||||
void clear_style_rule_actors();
|
||||
void received_applied_style_rules(JsonObject&, JsonValue const&);
|
||||
void received_dom_node_properties(WebView::DOMNodeProperties const&);
|
||||
|
||||
WeakPtr<InspectorActor> m_inspector;
|
||||
|
||||
Vector<Message, 1> m_pending_inspect_requests;
|
||||
Vector<String> m_style_rule_actors;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
63
Libraries/LibDevTools/Actors/StyleRuleActor.cpp
Normal file
63
Libraries/LibDevTools/Actors/StyleRuleActor.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/JsonArray.h>
|
||||
#include <LibDevTools/Actors/StyleRuleActor.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
NonnullRefPtr<StyleRuleActor> StyleRuleActor::create(DevToolsServer& devtools, String name, JsonObject rule)
|
||||
{
|
||||
return adopt_ref(*new StyleRuleActor(devtools, move(name), move(rule)));
|
||||
}
|
||||
|
||||
StyleRuleActor::StyleRuleActor(DevToolsServer& devtools, String name, JsonObject rule)
|
||||
: Actor(devtools, move(name))
|
||||
, m_rule(move(rule))
|
||||
{
|
||||
m_rule.set("actor"sv, this->name());
|
||||
|
||||
JsonObject traits;
|
||||
// FIXME: Support modifying style rules inside the inspector.
|
||||
traits.set("canSetRuleText"sv, false);
|
||||
m_rule.set("traits"sv, move(traits));
|
||||
m_rule.set("ancestorData"sv, JsonArray {});
|
||||
}
|
||||
|
||||
StyleRuleActor::~StyleRuleActor() = default;
|
||||
|
||||
JsonObject StyleRuleActor::serialize_rule() const
|
||||
{
|
||||
return m_rule;
|
||||
}
|
||||
|
||||
void StyleRuleActor::handle_message(Message const& message)
|
||||
{
|
||||
JsonObject response;
|
||||
|
||||
if (message.type == "getRuleText"sv) {
|
||||
response.set("text"sv, m_rule.get_string("cssText"sv).value_or({}));
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "modifyProperties"sv || message.type == "setRuleText"sv || message.type == "modifySelector"sv) {
|
||||
response.set("error"sv, "unsupported"sv);
|
||||
response.set("message"sv, "Live CSS rule editing is not supported"sv);
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type == "getQueryContainerForNode"sv) {
|
||||
response.set("container"sv, JsonValue {});
|
||||
send_response(message, move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
send_unrecognized_packet_type_error(message);
|
||||
}
|
||||
|
||||
}
|
||||
32
Libraries/LibDevTools/Actors/StyleRuleActor.h
Normal file
32
Libraries/LibDevTools/Actors/StyleRuleActor.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <LibDevTools/Actor.h>
|
||||
|
||||
namespace DevTools {
|
||||
|
||||
class DEVTOOLS_API StyleRuleActor final : public Actor {
|
||||
public:
|
||||
static constexpr auto base_name = "style-rule"sv;
|
||||
|
||||
static NonnullRefPtr<StyleRuleActor> create(DevToolsServer&, String name, JsonObject rule);
|
||||
virtual ~StyleRuleActor() override;
|
||||
|
||||
JsonObject serialize_rule() const;
|
||||
|
||||
private:
|
||||
StyleRuleActor(DevToolsServer&, String name, JsonObject rule);
|
||||
|
||||
virtual void handle_message(Message const&) override;
|
||||
|
||||
JsonObject m_rule;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ set(SOURCES
|
|||
Actors/PreferenceActor.cpp
|
||||
Actors/ProcessActor.cpp
|
||||
Actors/RootActor.cpp
|
||||
Actors/StyleRuleActor.cpp
|
||||
Actors/StyleSheetsActor.cpp
|
||||
Actors/TabActor.cpp
|
||||
Actors/TargetConfigurationActor.cpp
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class ParentAccessibilityActor;
|
|||
class PreferenceActor;
|
||||
class ProcessActor;
|
||||
class RootActor;
|
||||
class StyleRuleActor;
|
||||
class StyleSheetsActor;
|
||||
class TabActor;
|
||||
class TargetConfigurationActor;
|
||||
|
|
|
|||
|
|
@ -643,16 +643,6 @@ void CSSStyleSheet::recalculate_rule_caches()
|
|||
}
|
||||
}
|
||||
|
||||
void CSSStyleSheet::set_source_text(String source)
|
||||
{
|
||||
m_source_text = move(source);
|
||||
}
|
||||
|
||||
Optional<String> CSSStyleSheet::source_text(Badge<DOM::Document>) const
|
||||
{
|
||||
return m_source_text;
|
||||
}
|
||||
|
||||
void CSSStyleSheet::add_critical_subresource(Subresource& subresource)
|
||||
{
|
||||
m_critical_subresources.append(subresource);
|
||||
|
|
|
|||
|
|
@ -122,8 +122,8 @@ public:
|
|||
|
||||
bool disallow_modification() const { return m_disallow_modification; }
|
||||
|
||||
void set_source_text(String);
|
||||
Optional<String> source_text(Badge<DOM::Document>) const;
|
||||
void set_source_text(String source) { m_source_text = move(source); }
|
||||
Optional<String> source_text() const { return m_source_text; }
|
||||
|
||||
void add_critical_subresource(Subresource&);
|
||||
void remove_critical_subresource(Subresource&);
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@
|
|||
#include <AK/FixedBitmap.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/Math.h>
|
||||
#include <AK/NeverDestroyed.h>
|
||||
#include <AK/NonnullRawPtr.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibGfx/Font/FontDatabase.h>
|
||||
#include <LibWeb/Animations/AnimationEffect.h>
|
||||
#include <LibWeb/Animations/DocumentTimeline.h>
|
||||
|
|
@ -33,6 +35,7 @@
|
|||
#include <LibWeb/CSS/CSSScopeRule.h>
|
||||
#include <LibWeb/CSS/CSSStyleProperties.h>
|
||||
#include <LibWeb/CSS/CSSStyleRule.h>
|
||||
#include <LibWeb/CSS/CSSStyleSheet.h>
|
||||
#include <LibWeb/CSS/CSSTransition.h>
|
||||
#include <LibWeb/CSS/CascadedProperties.h>
|
||||
#include <LibWeb/CSS/ComputedProperties.h>
|
||||
|
|
@ -81,6 +84,7 @@
|
|||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/DOM/ShadowRoot.h>
|
||||
#include <LibWeb/HTML/AttributeNames.h>
|
||||
#include <LibWeb/HTML/HTMLBRElement.h>
|
||||
#include <LibWeb/HTML/HTMLHtmlElement.h>
|
||||
#include <LibWeb/HTML/HTMLImageElement.h>
|
||||
|
|
@ -1491,6 +1495,395 @@ StyleComputer::MatchingRuleSet StyleComputer::build_matching_rule_set(DOM::Abstr
|
|||
return matching_rule_set;
|
||||
}
|
||||
|
||||
static bool custom_property_inherits(DOM::Document const& document, FlyString const& name)
|
||||
{
|
||||
// A custom property inherits unless it has been registered with an explicit `inherits: false`.
|
||||
auto registration = document.get_registered_custom_property(name);
|
||||
return !registration.has_value() || registration->inherit;
|
||||
}
|
||||
|
||||
enum class IsCustomProperty : u8 {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
enum class Inherits : u8 {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
enum class NameIsValid : u8 {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
enum class IsValid : u8 {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
static JsonObject serialize_devtools_style_declaration(
|
||||
String name,
|
||||
String value,
|
||||
Important important,
|
||||
IsCustomProperty is_custom_property,
|
||||
Inherits inherits,
|
||||
NameIsValid is_name_valid,
|
||||
IsValid is_valid)
|
||||
{
|
||||
JsonObject serialized_property;
|
||||
serialized_property.set("name"sv, move(name));
|
||||
serialized_property.set("value"sv, move(value));
|
||||
serialized_property.set("priority"sv, important == Important::Yes ? "important"sv : ""sv);
|
||||
serialized_property.set("isCustomProperty"sv, is_custom_property == IsCustomProperty::Yes);
|
||||
serialized_property.set("inherits"sv, inherits == Inherits::Yes);
|
||||
serialized_property.set("isNameValid"sv, is_name_valid == NameIsValid::Yes);
|
||||
serialized_property.set("isValid"sv, is_valid == IsValid::Yes);
|
||||
return serialized_property;
|
||||
}
|
||||
|
||||
static JsonArray serialize_devtools_style_declarations(DOM::Document const& document, CSSStyleProperties const& declaration)
|
||||
{
|
||||
JsonArray declarations;
|
||||
|
||||
auto serialize_property = [&](String name, StyleProperty const& property, IsCustomProperty is_custom_property, Inherits inherits) {
|
||||
declarations.must_append(serialize_devtools_style_declaration(
|
||||
move(name),
|
||||
property.value->to_string(SerializationMode::Normal),
|
||||
property.important,
|
||||
is_custom_property,
|
||||
inherits,
|
||||
NameIsValid::Yes,
|
||||
IsValid::Yes));
|
||||
};
|
||||
|
||||
for (auto const& property : declaration.properties()) {
|
||||
serialize_property(
|
||||
string_from_property_id(property.property_id).to_string(),
|
||||
property,
|
||||
IsCustomProperty::No,
|
||||
is_inherited_property(property.property_id) ? Inherits::Yes : Inherits::No);
|
||||
}
|
||||
|
||||
for (auto const& custom_property : declaration.custom_properties())
|
||||
serialize_property(
|
||||
custom_property.key.to_string(),
|
||||
custom_property.value,
|
||||
IsCustomProperty::Yes,
|
||||
custom_property_inherits(document, custom_property.key) ? Inherits::Yes : Inherits::No);
|
||||
|
||||
return declarations;
|
||||
}
|
||||
|
||||
static JsonArray serialize_devtools_style_declarations(DOM::Document const& document, Vector<Parser::DevToolsStyleDeclaration> const& declarations)
|
||||
{
|
||||
JsonArray serialized_declarations;
|
||||
|
||||
for (auto const& declaration : declarations) {
|
||||
bool inherits = declaration.is_custom_property
|
||||
? custom_property_inherits(document, declaration.name)
|
||||
: PropertyNameAndID::from_name(declaration.name)
|
||||
.map([](auto const& property) { return !property.is_custom_property() && is_inherited_property(property.id()); })
|
||||
.value_or(false);
|
||||
|
||||
serialized_declarations.must_append(serialize_devtools_style_declaration(
|
||||
declaration.name.to_string(),
|
||||
declaration.value,
|
||||
declaration.important,
|
||||
declaration.is_custom_property ? IsCustomProperty::Yes : IsCustomProperty::No,
|
||||
inherits ? Inherits::Yes : Inherits::No,
|
||||
declaration.is_name_valid ? NameIsValid::Yes : NameIsValid::No,
|
||||
declaration.is_valid ? IsValid::Yes : IsValid::No));
|
||||
}
|
||||
|
||||
return serialized_declarations;
|
||||
}
|
||||
|
||||
static Vector<Parser::DevToolsStyleDeclaration> parse_devtools_style_declarations(DOM::Document const& document, StringView declaration_block)
|
||||
{
|
||||
return Parser::parse_css_declaration_block_for_devtools(Parser::ParsingParams(document), declaration_block);
|
||||
}
|
||||
|
||||
static Optional<size_t> source_offset_for_line_and_column(StringView source, SourcePosition const& position)
|
||||
{
|
||||
size_t line = 0;
|
||||
size_t column = 0;
|
||||
|
||||
Utf8View source_code_points { source };
|
||||
for (auto it = source_code_points.begin(); it != source_code_points.end();) {
|
||||
auto offset = source_code_points.byte_offset_of(it);
|
||||
if (line == position.line && column == position.column)
|
||||
return offset;
|
||||
|
||||
auto code_point = *it;
|
||||
++it;
|
||||
|
||||
if (code_point == '\r') {
|
||||
if (offset + 1 < source.length() && source[offset + 1] == '\n')
|
||||
++it;
|
||||
++line;
|
||||
column = 0;
|
||||
} else if (code_point == '\n' || code_point == '\f') {
|
||||
++line;
|
||||
column = 0;
|
||||
} else {
|
||||
++column;
|
||||
}
|
||||
}
|
||||
|
||||
if (line == position.line && column == position.column)
|
||||
return source.length();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Optional<String> extract_css_declaration_block_from_source(CSSRule const& rule)
|
||||
{
|
||||
if (rule.type() != CSSRule::Type::Style)
|
||||
return {};
|
||||
|
||||
auto const* style_sheet = rule.parent_style_sheet();
|
||||
if (!style_sheet)
|
||||
return {};
|
||||
|
||||
auto const source_text = style_sheet->source_text();
|
||||
if (!source_text.has_value())
|
||||
return {};
|
||||
|
||||
auto const& source = *source_text;
|
||||
auto source_view = source.bytes_as_string_view();
|
||||
auto const& source_location = rule.source_location();
|
||||
if (!source_location.has_value())
|
||||
return {};
|
||||
|
||||
auto maybe_offset = source_offset_for_line_and_column(source_view, *source_location);
|
||||
if (!maybe_offset.has_value())
|
||||
return {};
|
||||
|
||||
Optional<u8> string_quote;
|
||||
bool in_comment = false;
|
||||
bool escaped = false;
|
||||
Optional<size_t> block_start;
|
||||
size_t block_depth = 0;
|
||||
|
||||
for (size_t offset = *maybe_offset; offset < source_view.length(); ++offset) {
|
||||
auto ch = source_view[offset];
|
||||
auto next_ch = offset + 1 < source_view.length() ? source_view[offset + 1] : '\0';
|
||||
|
||||
if (in_comment) {
|
||||
if (ch == '*' && next_ch == '/') {
|
||||
in_comment = false;
|
||||
++offset;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string_quote.has_value()) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (ch == '\\') {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if (ch == *string_quote)
|
||||
string_quote = {};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == '/' && next_ch == '*') {
|
||||
in_comment = true;
|
||||
++offset;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == '"' || ch == '\'') {
|
||||
string_quote = ch;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == '{') {
|
||||
if (!block_start.has_value())
|
||||
block_start = offset + 1;
|
||||
++block_depth;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == '}' && block_start.has_value()) {
|
||||
VERIFY(block_depth > 0);
|
||||
--block_depth;
|
||||
if (block_depth == 0)
|
||||
return MUST(String::from_utf8(source_view.substring_view(*block_start, offset - *block_start)));
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool has_inherited_declaration(DOM::Document const& document, CSSStyleProperties const& declaration)
|
||||
{
|
||||
if (any_of(declaration.properties(), [](auto const& property) {
|
||||
return CSS::is_inherited_property(property.property_id);
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return any_of(declaration.custom_properties(), [&](auto const& custom_property) {
|
||||
return custom_property_inherits(document, custom_property.key);
|
||||
});
|
||||
}
|
||||
|
||||
static JsonArray serialize_devtools_selectors(MatchingRule const& rule)
|
||||
{
|
||||
JsonArray selectors;
|
||||
for (auto const& selector : rule.absolutized_selectors())
|
||||
selectors.must_append(selector->serialize());
|
||||
return selectors;
|
||||
}
|
||||
|
||||
static JsonArray serialize_devtools_selector_specificities(MatchingRule const& rule)
|
||||
{
|
||||
JsonArray specificities;
|
||||
for (auto const& selector : rule.absolutized_selectors())
|
||||
specificities.must_append(selector->specificity());
|
||||
return specificities;
|
||||
}
|
||||
|
||||
static JsonObject serialize_devtools_matching_rule(DOM::Document const& document, MatchingRule const& rule)
|
||||
{
|
||||
auto const& declaration = rule.declaration();
|
||||
auto authored_text = extract_css_declaration_block_from_source(*rule.rule);
|
||||
|
||||
JsonArray matched_selector_indexes;
|
||||
matched_selector_indexes.must_append(rule.selector_index);
|
||||
|
||||
JsonObject serialized_rule;
|
||||
serialized_rule.set("type"sv, to_underlying(rule.rule->type()));
|
||||
serialized_rule.set("className"sv, rule.rule->type() == CSSRule::Type::Style ? "CSSStyleRule"sv : "CSSNestedDeclarations"sv);
|
||||
serialized_rule.set("selectors"sv, serialize_devtools_selectors(rule));
|
||||
serialized_rule.set("selectorsSpecificity"sv, serialize_devtools_selector_specificities(rule));
|
||||
serialized_rule.set("matchedSelectorIndexes"sv, move(matched_selector_indexes));
|
||||
serialized_rule.set("cssText"sv, rule.rule->css_text());
|
||||
if (authored_text.has_value()) {
|
||||
serialized_rule.set("authoredText"sv, *authored_text);
|
||||
serialized_rule.set("declarations"sv, serialize_devtools_style_declarations(document, parse_devtools_style_declarations(document, authored_text->bytes_as_string_view())));
|
||||
} else {
|
||||
serialized_rule.set("authoredText"sv, declaration.serialized());
|
||||
serialized_rule.set("declarations"sv, serialize_devtools_style_declarations(document, declaration));
|
||||
}
|
||||
serialized_rule.set("styleSheetIndex"sv, rule.style_sheet_index);
|
||||
serialized_rule.set("ruleIndex"sv, rule.rule_index);
|
||||
serialized_rule.set("isSystem"sv, rule.cascade_origin == CascadeOrigin::UserAgent);
|
||||
|
||||
return serialized_rule;
|
||||
}
|
||||
|
||||
static JsonObject serialize_devtools_inline_style(DOM::Document const& document, DOM::AbstractElement abstract_element, CSSStyleProperties const& declaration)
|
||||
{
|
||||
auto authored_text = abstract_element.element().get_attribute(HTML::AttributeNames::style);
|
||||
|
||||
JsonObject serialized_rule;
|
||||
serialized_rule.set("type"sv, 100);
|
||||
serialized_rule.set("className"sv, 100);
|
||||
serialized_rule.set("cssText"sv, declaration.serialized());
|
||||
if (authored_text.has_value()) {
|
||||
serialized_rule.set("authoredText"sv, *authored_text);
|
||||
serialized_rule.set("declarations"sv, serialize_devtools_style_declarations(document, parse_devtools_style_declarations(document, authored_text->bytes_as_string_view())));
|
||||
} else {
|
||||
serialized_rule.set("authoredText"sv, declaration.serialized());
|
||||
serialized_rule.set("declarations"sv, serialize_devtools_style_declarations(document, declaration));
|
||||
}
|
||||
serialized_rule.set("isSystem"sv, false);
|
||||
serialized_rule.set("nodeId"sv, abstract_element.element().unique_id().value());
|
||||
return serialized_rule;
|
||||
}
|
||||
|
||||
static void append_devtools_applied_style_entry(JsonArray& entries, JsonObject rule, Optional<UniqueNodeID> inherited_node_id = {})
|
||||
{
|
||||
JsonObject entry;
|
||||
|
||||
JsonValue matched_selector_indexes { JsonArray {} };
|
||||
if (auto value = rule.get("matchedSelectorIndexes"sv); value.has_value())
|
||||
matched_selector_indexes = *value;
|
||||
rule.remove("matchedSelectorIndexes"sv);
|
||||
auto is_system = rule.get_bool("isSystem"sv).value_or(false);
|
||||
|
||||
entry.set("rule"sv, move(rule));
|
||||
entry.set("isSystem"sv, is_system);
|
||||
entry.set("matchedSelectorIndexes"sv, move(matched_selector_indexes));
|
||||
if (inherited_node_id.has_value())
|
||||
entry.set("inheritedNodeId"sv, inherited_node_id->value());
|
||||
else
|
||||
entry.set("inherited"sv, JsonValue {});
|
||||
|
||||
entries.must_append(move(entry));
|
||||
}
|
||||
|
||||
static void append_devtools_rules_for_element(DOM::Document const& document, JsonArray& entries, auto const& matching_rule_set, bool include_user_agent_styles, Optional<UniqueNodeID> inherited_node_id = {})
|
||||
{
|
||||
auto should_include_rule = [&](MatchingRule const& rule) {
|
||||
return !inherited_node_id.has_value() || has_inherited_declaration(document, rule.declaration());
|
||||
};
|
||||
|
||||
auto append_rules = [&](auto const& matching_rules) {
|
||||
for (auto const& matching_rule : matching_rules.in_reverse()) {
|
||||
auto const& rule = *matching_rule.rule;
|
||||
if (!should_include_rule(rule))
|
||||
continue;
|
||||
append_devtools_applied_style_entry(entries, serialize_devtools_matching_rule(document, rule), inherited_node_id);
|
||||
}
|
||||
};
|
||||
|
||||
for (auto const& context : matching_rule_set.author_contexts.in_reverse()) {
|
||||
for (auto const& layer : context.author_rules.in_reverse())
|
||||
append_rules(layer.rules);
|
||||
}
|
||||
append_rules(matching_rule_set.user_rules);
|
||||
if (include_user_agent_styles)
|
||||
append_rules(matching_rule_set.user_agent_rules);
|
||||
}
|
||||
|
||||
JsonArray StyleComputer::collect_devtools_applied_style_rules(DOM::AbstractElement abstract_element, bool include_inherited, bool include_user_agent_styles)
|
||||
{
|
||||
JsonArray entries;
|
||||
|
||||
auto append_rules_for_abstract_element = [&](DOM::AbstractElement current_element, Optional<UniqueNodeID> inherited_node_id) {
|
||||
if (auto inline_style = current_element.inline_style()) {
|
||||
if (!inherited_node_id.has_value() || has_inherited_declaration(m_document, *inline_style))
|
||||
append_devtools_applied_style_entry(entries, serialize_devtools_inline_style(m_document, current_element, *inline_style), inherited_node_id);
|
||||
}
|
||||
|
||||
auto const first_ancestor = [&] -> GC::Ptr<DOM::Element const> {
|
||||
if (current_element.pseudo_element().has_value())
|
||||
return ¤t_element.element();
|
||||
return current_element.element().parent_or_shadow_host_element();
|
||||
}();
|
||||
|
||||
for (auto ancestor = first_ancestor; ancestor; ancestor = ancestor->parent_or_shadow_host_element())
|
||||
push_ancestor(*ancestor);
|
||||
|
||||
ScopeGuard pop_ancestors = [&] {
|
||||
for (auto ancestor = first_ancestor; ancestor; ancestor = ancestor->parent_or_shadow_host_element())
|
||||
pop_ancestor(*ancestor);
|
||||
};
|
||||
|
||||
bool did_match_any_pseudo_element_rules = false;
|
||||
auto matching_rule_set = build_matching_rule_set(current_element, did_match_any_pseudo_element_rules, ComputeStyleMode::Normal);
|
||||
append_devtools_rules_for_element(m_document, entries, matching_rule_set, include_user_agent_styles, inherited_node_id);
|
||||
};
|
||||
|
||||
append_rules_for_abstract_element(abstract_element, {});
|
||||
|
||||
if (!include_inherited)
|
||||
return entries;
|
||||
|
||||
for (auto current_element = abstract_element.element_to_inherit_style_from(); current_element.has_value(); current_element = current_element->element_to_inherit_style_from())
|
||||
append_rules_for_abstract_element(*current_element, current_element->element().unique_id());
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-cascade/#cascading
|
||||
// https://drafts.csswg.org/css-cascade-5/#layering
|
||||
GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::AbstractElement abstract_element, bool did_match_any_pseudo_element_rules, ComputeStyleMode mode, MatchingRuleSet const& matching_rule_set) const
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <LibWeb/Animations/KeyframeEffect.h>
|
||||
|
|
@ -105,6 +106,7 @@ public:
|
|||
[[nodiscard]] GC::Ref<ComputedProperties> compute_style(DOM::AbstractElement, Optional<bool&> did_change_custom_properties = {}) const;
|
||||
[[nodiscard]] GC::Ref<ComputedProperties> compute_style_with_seeded_ancestors(DOM::AbstractElement);
|
||||
[[nodiscard]] GC::Ptr<ComputedProperties> compute_pseudo_element_style_if_needed(DOM::AbstractElement, Optional<bool&> did_change_custom_properties) const;
|
||||
[[nodiscard]] JsonArray collect_devtools_applied_style_rules(DOM::AbstractElement, bool include_inherited, bool include_user_agent_styles);
|
||||
|
||||
struct ScopedMatchingRule {
|
||||
MatchingRule const* rule { nullptr };
|
||||
|
|
|
|||
|
|
@ -410,7 +410,8 @@ void StyleScope::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin
|
|||
if (scope_rule)
|
||||
collect_scope_boundary_selector_dependencies(*scope_rule, style_cache);
|
||||
|
||||
for (CSS::Selector const& selector : absolutized_selectors) {
|
||||
for (size_t selector_index = 0; selector_index < absolutized_selectors.size(); ++selector_index) {
|
||||
auto const& selector = *absolutized_selectors[selector_index];
|
||||
MatchingRule matching_rule {
|
||||
.rule = &rule,
|
||||
.sheet = current_style_sheet,
|
||||
|
|
@ -418,6 +419,7 @@ void StyleScope::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_origin
|
|||
.scope_rule = scope_rule,
|
||||
.default_namespace = current_style_sheet.default_namespace(),
|
||||
.selector = selector,
|
||||
.selector_index = selector_index,
|
||||
.style_sheet_index = style_sheet_index,
|
||||
.rule_index = rule_index,
|
||||
.specificity = selector.specificity(),
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ struct MatchingRule {
|
|||
GC::Ptr<CSSRule const> scope_rule; // Either CSSScopeRule or CSSImportRule
|
||||
Optional<FlyString> default_namespace;
|
||||
Selector const& selector;
|
||||
size_t selector_index { 0 };
|
||||
size_t style_sheet_index { 0 };
|
||||
size_t rule_index { 0 };
|
||||
|
||||
|
|
|
|||
|
|
@ -7664,11 +7664,11 @@ Optional<String> Document::get_style_sheet_source(CSS::StyleSheetIdentifier cons
|
|||
if (auto* node = Node::from_unique_id(*identifier.dom_element_unique_id)) {
|
||||
if (node->is_html_style_element()) {
|
||||
if (auto* sheet = as<HTML::HTMLStyleElement>(*node).sheet())
|
||||
return sheet->source_text({});
|
||||
return sheet->source_text();
|
||||
}
|
||||
if (node->is_svg_style_element()) {
|
||||
if (auto* sheet = as<SVG::SVGStyleElement>(*node).sheet())
|
||||
return sheet->source_text({});
|
||||
return sheet->source_text();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7683,7 +7683,7 @@ Optional<String> Document::get_style_sheet_source(CSS::StyleSheetIdentifier cons
|
|||
if (m_style_sheets) {
|
||||
for (auto& style_sheet : m_style_sheets->sheets()) {
|
||||
if (auto match = find_style_sheet_with_url(identifier.url.value(), style_sheet); match.has_value())
|
||||
return match->source_text({});
|
||||
return match->source_text();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7694,7 +7694,7 @@ Optional<String> Document::get_style_sheet_source(CSS::StyleSheetIdentifier cons
|
|||
return;
|
||||
|
||||
if (auto match = find_style_sheet_with_url(identifier.url.value(), style_sheet); match.has_value())
|
||||
result = match->source_text({});
|
||||
result = match->source_text();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace WebView {
|
|||
|
||||
struct WEBVIEW_API DOMNodeProperties {
|
||||
enum class Type {
|
||||
AppliedStyleRules,
|
||||
ComputedStyle,
|
||||
Layout,
|
||||
UsedFonts,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include <LibWeb/CSS/StyleSheetList.h>
|
||||
#include <LibWeb/Compositor/CompositorHost.h>
|
||||
#include <LibWeb/CookieStore/CookieStore.h>
|
||||
#include <LibWeb/DOM/AbstractElement.h>
|
||||
#include <LibWeb/DOM/Attr.h>
|
||||
#include <LibWeb/DOM/CharacterData.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
|
|
@ -548,8 +549,6 @@ void ConnectionFromClient::inspect_dom_tree(u64 page_id)
|
|||
|
||||
void ConnectionFromClient::inspect_dom_node(u64 page_id, WebView::DOMNodeProperties::Type property_type, Web::UniqueNodeID node_id, Optional<Web::CSS::PseudoElement> pseudo_element, JsonValue options_value)
|
||||
{
|
||||
(void)options_value;
|
||||
|
||||
auto page = this->page(page_id);
|
||||
if (!page.has_value())
|
||||
return;
|
||||
|
|
@ -557,8 +556,7 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, WebView::DOMNodePropert
|
|||
clear_inspected_dom_node(page_id);
|
||||
|
||||
auto* node = Web::DOM::Node::from_unique_id(node_id);
|
||||
// Nodes without layout (aka non-visible nodes) don't have style computed.
|
||||
if (!node || !node->layout_node() || !node->is_element()) {
|
||||
if (!node || !node->is_element()) {
|
||||
async_did_inspect_dom_node(page_id, { property_type, {} });
|
||||
return;
|
||||
}
|
||||
|
|
@ -566,6 +564,9 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, WebView::DOMNodePropert
|
|||
auto& element = as<Web::DOM::Element>(*node);
|
||||
node->document().set_inspected_node(node);
|
||||
|
||||
Web::DOM::AbstractElement abstract_element { element, pseudo_element };
|
||||
node->document().update_style_for_element(abstract_element);
|
||||
|
||||
auto properties = element.computed_properties(pseudo_element);
|
||||
|
||||
if (!properties) {
|
||||
|
|
@ -573,6 +574,13 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, WebView::DOMNodePropert
|
|||
return;
|
||||
}
|
||||
|
||||
// Nodes without layout (aka non-visible nodes) do not have box metrics, but DevTools can still ask for their style
|
||||
// rules and computed properties.
|
||||
if (property_type == WebView::DOMNodeProperties::Type::Layout && !node->layout_node()) {
|
||||
async_did_inspect_dom_node(page_id, { property_type, {} });
|
||||
return;
|
||||
}
|
||||
|
||||
auto serialize_computed_style = [&]() {
|
||||
JsonObject serialized;
|
||||
|
||||
|
|
@ -647,9 +655,20 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, WebView::DOMNodePropert
|
|||
return serialized;
|
||||
};
|
||||
|
||||
auto serialize_applied_style_rules = [&]() {
|
||||
JsonObject const empty_options;
|
||||
auto const& options = options_value.is_object() ? options_value.as_object() : empty_options;
|
||||
auto include_inherited = options.get_bool("inherited"sv).value_or(false);
|
||||
auto include_user_agent_styles = options.get_string("filter"sv).map([](auto const& filter) { return filter == "ua"sv; }).value_or(false);
|
||||
return node->document().style_computer().collect_devtools_applied_style_rules(abstract_element, include_inherited, include_user_agent_styles);
|
||||
};
|
||||
|
||||
JsonValue serialized;
|
||||
|
||||
switch (property_type) {
|
||||
case WebView::DOMNodeProperties::Type::AppliedStyleRules:
|
||||
serialized = serialize_applied_style_rules();
|
||||
break;
|
||||
case WebView::DOMNodeProperties::Type::ComputedStyle:
|
||||
serialized = serialize_computed_style();
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -188,6 +188,84 @@ static WebView::DOMNodeProperties make_computed_style()
|
|||
return { WebView::DOMNodeProperties::Type::ComputedStyle, move(properties) };
|
||||
}
|
||||
|
||||
static WebView::DOMNodeProperties make_applied_style_rules()
|
||||
{
|
||||
JsonArray entries;
|
||||
|
||||
JsonArray inline_declarations;
|
||||
JsonObject inline_display;
|
||||
inline_display.set("name"sv, "display"sv);
|
||||
inline_display.set("value"sv, "grid"sv);
|
||||
inline_display.set("priority"sv, ""sv);
|
||||
inline_display.set("isCustomProperty"sv, false);
|
||||
inline_display.set("isNameValid"sv, true);
|
||||
inline_display.set("isValid"sv, true);
|
||||
inline_display.set("inherits"sv, false);
|
||||
inline_declarations.must_append(move(inline_display));
|
||||
|
||||
JsonObject inline_rule;
|
||||
inline_rule.set("type"sv, 100);
|
||||
inline_rule.set("className"sv, 100);
|
||||
inline_rule.set("cssText"sv, "display: grid;"sv);
|
||||
inline_rule.set("authoredText"sv, "display: grid;"sv);
|
||||
inline_rule.set("declarations"sv, move(inline_declarations));
|
||||
|
||||
JsonObject inline_entry;
|
||||
inline_entry.set("rule"sv, move(inline_rule));
|
||||
inline_entry.set("isSystem"sv, false);
|
||||
inline_entry.set("matchedSelectorIndexes"sv, JsonArray {});
|
||||
inline_entry.set("inherited"sv, JsonValue {});
|
||||
entries.must_append(move(inline_entry));
|
||||
|
||||
JsonArray rule_declarations;
|
||||
JsonObject rule_color;
|
||||
rule_color.set("name"sv, "color"sv);
|
||||
rule_color.set("value"sv, "rgb(1, 2, 3)"sv);
|
||||
rule_color.set("priority"sv, ""sv);
|
||||
rule_color.set("isCustomProperty"sv, false);
|
||||
rule_color.set("isNameValid"sv, true);
|
||||
rule_color.set("isValid"sv, true);
|
||||
rule_color.set("inherits"sv, true);
|
||||
rule_declarations.must_append(move(rule_color));
|
||||
|
||||
JsonObject rule_invalid_color;
|
||||
rule_invalid_color.set("name"sv, "color"sv);
|
||||
rule_invalid_color.set("value"sv, "invalid-value"sv);
|
||||
rule_invalid_color.set("priority"sv, ""sv);
|
||||
rule_invalid_color.set("isCustomProperty"sv, false);
|
||||
rule_invalid_color.set("isNameValid"sv, true);
|
||||
rule_invalid_color.set("isValid"sv, false);
|
||||
rule_invalid_color.set("inherits"sv, true);
|
||||
rule_declarations.must_append(move(rule_invalid_color));
|
||||
|
||||
JsonArray selectors;
|
||||
selectors.must_append("body div.fixture"sv);
|
||||
|
||||
JsonArray selector_specificities;
|
||||
selector_specificities.must_append(0x101u);
|
||||
|
||||
JsonArray matched_selector_indexes;
|
||||
matched_selector_indexes.must_append(0u);
|
||||
|
||||
JsonObject rule;
|
||||
rule.set("type"sv, 1);
|
||||
rule.set("className"sv, "CSSStyleRule"sv);
|
||||
rule.set("selectors"sv, move(selectors));
|
||||
rule.set("selectorsSpecificity"sv, move(selector_specificities));
|
||||
rule.set("cssText"sv, "body div.fixture { color: rgb(1, 2, 3); }"sv);
|
||||
rule.set("authoredText"sv, "color: rgb(1, 2, 3);"sv);
|
||||
rule.set("declarations"sv, move(rule_declarations));
|
||||
|
||||
JsonObject rule_entry;
|
||||
rule_entry.set("rule"sv, move(rule));
|
||||
rule_entry.set("isSystem"sv, false);
|
||||
rule_entry.set("matchedSelectorIndexes"sv, move(matched_selector_indexes));
|
||||
rule_entry.set("inheritedNodeId"sv, 1u);
|
||||
entries.must_append(move(rule_entry));
|
||||
|
||||
return { WebView::DOMNodeProperties::Type::AppliedStyleRules, move(entries) };
|
||||
}
|
||||
|
||||
static WebView::DOMNodeProperties make_layout()
|
||||
{
|
||||
JsonObject properties;
|
||||
|
|
@ -420,14 +498,16 @@ public:
|
|||
|
||||
virtual void inspect_dom_node(DevTools::TabDescription const&, WebView::DOMNodeProperties::Type type, Web::UniqueNodeID node_id, Optional<Web::CSS::PseudoElement> pseudo_element, JsonObject options = {}) const override
|
||||
{
|
||||
(void)options;
|
||||
++inspect_dom_node_call_count;
|
||||
last_inspected_dom_node = node_id;
|
||||
last_inspected_pseudo_element = pseudo_element;
|
||||
last_inspected_dom_node_options = move(options);
|
||||
|
||||
Core::deferred_invoke([this, type] {
|
||||
VERIFY(on_dom_node_properties);
|
||||
if (type == WebView::DOMNodeProperties::Type::ComputedStyle)
|
||||
if (type == WebView::DOMNodeProperties::Type::AppliedStyleRules)
|
||||
on_dom_node_properties(make_applied_style_rules());
|
||||
else if (type == WebView::DOMNodeProperties::Type::ComputedStyle)
|
||||
on_dom_node_properties(make_computed_style());
|
||||
else if (type == WebView::DOMNodeProperties::Type::Layout)
|
||||
on_dom_node_properties(make_layout());
|
||||
|
|
@ -854,6 +934,7 @@ public:
|
|||
mutable Optional<Web::CSS::PseudoElement> last_highlighted_pseudo_element;
|
||||
mutable Optional<Web::UniqueNodeID> last_inspected_dom_node;
|
||||
mutable Optional<Web::CSS::PseudoElement> last_inspected_pseudo_element;
|
||||
mutable JsonObject last_inspected_dom_node_options;
|
||||
mutable Optional<Web::UniqueNodeID> last_grid_root_node;
|
||||
mutable Optional<Web::UniqueNodeID> last_current_grid_node;
|
||||
mutable Optional<Web::UniqueNodeID> last_current_flexbox_node;
|
||||
|
|
@ -1031,6 +1112,16 @@ static JsonObject get_tab(ProtocolClient& client)
|
|||
return tabs.at(0).as_object();
|
||||
}
|
||||
|
||||
static size_t style_rule_actor_count(DevTools::DevToolsServer const& server)
|
||||
{
|
||||
size_t count = 0;
|
||||
for (auto const& actor : server.actor_registry()) {
|
||||
if (actor.key.bytes_as_string_view().contains("-style-rule"sv))
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static JsonObject get_frame_target(ProtocolClient& client, StringView tab_actor)
|
||||
{
|
||||
auto watcher_actor = actor_from(client.request(tab_actor, "getWatcher"sv), "actor"sv);
|
||||
|
|
@ -1856,12 +1947,51 @@ TEST_CASE(styles_and_stylesheets)
|
|||
auto root_actor = walker.get_object("root"sv)->get_string("actor"sv).release_value();
|
||||
auto div_actor = query_selector(client, walker_actor, root_actor, "div"sv);
|
||||
|
||||
JsonObject applied;
|
||||
applied.set("to"sv, page_style_actor);
|
||||
applied.set("type"sv, "getApplied"sv);
|
||||
EXPECT(client.request(move(applied)).get_array("entries"sv)->is_empty());
|
||||
auto get_applied = [&] {
|
||||
JsonObject applied;
|
||||
applied.set("to"sv, page_style_actor);
|
||||
applied.set("type"sv, "getApplied"sv);
|
||||
applied.set("node"sv, div_actor);
|
||||
applied.set("inherited"sv, true);
|
||||
applied.set("matchedSelectors"sv, true);
|
||||
return client.request(move(applied)).get_array("entries"sv).release_value();
|
||||
};
|
||||
|
||||
auto applied_entries = get_applied();
|
||||
EXPECT(session->delegate.last_inspected_dom_node_options.get_bool("inherited"sv).value());
|
||||
EXPECT(session->delegate.last_inspected_dom_node_options.get_bool("matchedSelectors"sv).value());
|
||||
VERIFY(applied_entries.size() == 2u);
|
||||
EXPECT_EQ(style_rule_actor_count(*session->server), applied_entries.size());
|
||||
|
||||
auto inline_rule = applied_entries.at(0).as_object().get_object("rule"sv).release_value();
|
||||
auto inline_rule_actor = actor_from(inline_rule, "actor"sv);
|
||||
EXPECT_EQ(inline_rule.get_integer<int>("type"sv).value(), 100);
|
||||
EXPECT(inline_rule.get_array("ancestorData"sv)->is_empty());
|
||||
EXPECT(!inline_rule.get_object("traits"sv)->get_bool("canSetRuleText"sv).value());
|
||||
EXPECT_EQ(inline_rule.get_array("declarations"sv)->at(0).as_object().get_string("name"sv).value(), "display"sv);
|
||||
EXPECT(inline_rule.get_array("declarations"sv)->at(0).as_object().get_bool("isNameValid"sv).value());
|
||||
EXPECT(inline_rule.get_array("declarations"sv)->at(0).as_object().get_bool("isValid"sv).value());
|
||||
EXPECT_EQ(client.request(inline_rule_actor, "getRuleText"sv).get_string("text"sv).value(), "display: grid;"sv);
|
||||
|
||||
auto inherited_rule_entry = applied_entries.at(1).as_object();
|
||||
auto inherited_rule = inherited_rule_entry.get_object("rule"sv).release_value();
|
||||
EXPECT_EQ(inherited_rule_entry.get_string("inherited"sv).value(), root_actor);
|
||||
EXPECT_EQ(inherited_rule.get_array("selectors"sv)->at(0).as_string(), "body div.fixture"sv);
|
||||
EXPECT_EQ(inherited_rule.get_array("declarations"sv)->at(0).as_object().get_string("name"sv).value(), "color"sv);
|
||||
EXPECT(inherited_rule.get_array("declarations"sv)->at(0).as_object().get_bool("isNameValid"sv).value());
|
||||
EXPECT(inherited_rule.get_array("declarations"sv)->at(0).as_object().get_bool("isValid"sv).value());
|
||||
EXPECT(inherited_rule.get_array("declarations"sv)->at(1).as_object().get_bool("isNameValid"sv).value());
|
||||
EXPECT(!inherited_rule.get_array("declarations"sv)->at(1).as_object().get_bool("isValid"sv).value());
|
||||
EXPECT_EQ(inherited_rule_entry.get_array("matchedSelectorIndexes"sv)->at(0).as_integer<int>(), 0);
|
||||
EXPECT(!client.request(page_style_actor, "isPositionEditable"sv).get_bool("value"sv).value());
|
||||
|
||||
auto second_applied_entries = get_applied();
|
||||
VERIFY(second_applied_entries.size() == applied_entries.size());
|
||||
spin_until(session->loop, [&] {
|
||||
return style_rule_actor_count(*session->server) == second_applied_entries.size()
|
||||
&& !session->server->actor_registry().contains(inline_rule_actor);
|
||||
});
|
||||
|
||||
JsonObject computed_request;
|
||||
computed_request.set("to"sv, page_style_actor);
|
||||
computed_request.set("type"sv, "getComputed"sv);
|
||||
|
|
|
|||
Loading…
Reference in a new issue