1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-11 02:13:56 +09:00

LibWeb: Resolve CSS custom properties on pseudo elements

The resolved property sets are stored with the element in a
per-pseudo-element array (same as for pseudo element layout nodes).

Longer term, we should stop storing this with elements entirely and make
it temporary state in StyleComputer somehow, so we don't waste memory
keeping all the resolved properties around.

This makes various gradients show up on https://shopify.com/ :^)
This commit is contained in:
Andreas Kling 2023-05-17 17:05:36 +02:00
parent 6970f1b6c1
commit fb722e69f3
Notes: sideshowbarker 2024-07-17 07:14:09 +09:00
9 changed files with 170 additions and 29 deletions

View file

@ -0,0 +1,60 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x470.195312 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x454.195312 children: not-inline
BlockContainer <(anonymous)> at (8,8) content-size 784x21.835937 children: inline
line 0 width: 391.640625, height: 21.835937, bottom: 21.835937, baseline: 16.914062
frag 0 from TextNode start: 0, length: 40, rect: [8,8 391.640625x21.835937]
"Variable set by inline style of element:"
TextNode <#text>
BreakNode <br>
TextNode <#text>
BlockContainer <div.a> at (8,29.835937) content-size 784x100 children: inline
line 0 width: 200, height: 100, bottom: 100, baseline: 16.914062
frag 0 from BlockContainer start: 0, length: 0, rect: [8,29.835937 200x100]
BlockContainer <(anonymous)> at (8,29.835937) content-size 200x100 inline-block [BFC] children: inline
line 0 width: 0, height: 21.835937, bottom: 21.835937, baseline: 16.914062
frag 0 from TextNode start: 0, length: 0, rect: [8,29.835937 0x21.835937]
""
TextNode <#text>
BlockContainer <(anonymous)> at (8,129.835937) content-size 784x66.179687 children: inline
line 0 width: 0, height: 21.835937, bottom: 21.835937, baseline: 16.914062
line 1 width: 0, height: 21.835937, bottom: 43.671875, baseline: 16.914062
line 2 width: 441.269531, height: 22.507812, bottom: 66.179687, baseline: 16.914062
frag 0 from TextNode start: 1, length: 42, rect: [8,172.835937 441.269531x21.835937]
"Variable set by CSS rule matching element:"
TextNode <#text>
BreakNode <br>
BreakNode <br>
TextNode <#text>
BreakNode <br>
TextNode <#text>
BlockContainer <div.b> at (8,196.015625) content-size 784x100 children: inline
line 0 width: 200, height: 100, bottom: 100, baseline: 16.914062
frag 0 from BlockContainer start: 0, length: 0, rect: [8,196.015625 200x100]
BlockContainer <(anonymous)> at (8,196.015625) content-size 200x100 inline-block [BFC] children: inline
line 0 width: 0, height: 21.835937, bottom: 21.835937, baseline: 16.914062
frag 0 from TextNode start: 0, length: 0, rect: [8,196.015625 0x21.835937]
""
TextNode <#text>
BlockContainer <(anonymous)> at (8,296.015625) content-size 784x66.179687 children: inline
line 0 width: 0, height: 21.835937, bottom: 21.835937, baseline: 16.914062
line 1 width: 0, height: 21.835937, bottom: 43.671875, baseline: 16.914062
line 2 width: 520.605468, height: 22.507812, bottom: 66.179687, baseline: 16.914062
frag 0 from TextNode start: 1, length: 49, rect: [8,339.015625 520.605468x21.835937]
"Variable set by CSS rule matching pseudo element:"
TextNode <#text>
BreakNode <br>
BreakNode <br>
TextNode <#text>
BreakNode <br>
TextNode <#text>
BlockContainer <div.c> at (8,362.195312) content-size 784x100 children: inline
line 0 width: 200, height: 100, bottom: 100, baseline: 16.914062
frag 0 from BlockContainer start: 0, length: 0, rect: [8,362.195312 200x100]
BlockContainer <(anonymous)> at (8,362.195312) content-size 200x100 inline-block [BFC] children: inline
line 0 width: 0, height: 21.835937, bottom: 21.835937, baseline: 16.914062
frag 0 from TextNode start: 0, length: 0, rect: [8,362.195312 0x21.835937]
""
TextNode <#text>
BlockContainer <(anonymous)> at (8,462.195312) content-size 784x0 children: inline
TextNode <#text>

View file

@ -0,0 +1,9 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x0 children: not-inline
BlockContainer <div.hello> at (8,8) content-size 784x0 children: not-inline
BlockContainer <(anonymous)> at (8,8) content-size 500x100 positioned [BFC] children: inline
line 0 width: 0, height: 21.835937, bottom: 21.835937, baseline: 16.914062
frag 0 from TextNode start: 0, length: 0, rect: [8,8 0x21.835937]
""
TextNode <#text>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html><style>
* {
font: 20px SerenitySans;
}
:root {
--bg: red;
--width: 100px;
}
div::before {
display: inline-block;
height: 100px;
content: "";
background: var(--bg);
width: var(--width);
}
.b {
--bg: green;
--width: 200px;
}
.c::before {
--bg: green;
--width: 200px;
}
</style>
Variable set by inline style of element:<br>
<div class="a" style="--bg: green; --width: 200px;"></div>
<br><br>
Variable set by CSS rule matching element:<br>
<div class="b"></div>
<br><br>
Variable set by CSS rule matching pseudo element:<br>
<div class="c"></div>

View file

@ -0,0 +1,15 @@
<!DOCTYPE html><style>
* {
font: 20px SerenitySans;
}
.hello::before {
position: absolute;
height: 100px;
width: 100px;
--wide: 500px;
width: var(--wide);
--bg: orange;
background: var(--bg);
content: "";
}
</style><div class="hello">

View file

@ -668,16 +668,21 @@ static void set_property_expanding_shorthands(StyleProperties& style, CSS::Prope
style.set_property(property_id, value);
}
static RefPtr<StyleValue const> get_custom_property(DOM::Element const& element, FlyString const& custom_property_name)
static RefPtr<StyleValue const> get_custom_property(DOM::Element const& element, Optional<CSS::Selector::PseudoElement> pseudo_element, FlyString const& custom_property_name)
{
if (pseudo_element.has_value()) {
if (auto it = element.custom_properties(pseudo_element).find(custom_property_name.to_string().to_deprecated_string()); it != element.custom_properties(pseudo_element).end())
return it->value.value;
}
for (auto const* current_element = &element; current_element; current_element = current_element->parent_element()) {
if (auto it = current_element->custom_properties().find(custom_property_name.to_string().to_deprecated_string()); it != current_element->custom_properties().end())
if (auto it = current_element->custom_properties({}).find(custom_property_name.to_string().to_deprecated_string()); it != current_element->custom_properties({}).end())
return it->value.value;
}
return nullptr;
}
bool StyleComputer::expand_variables(DOM::Element& element, StringView property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Parser::TokenStream<Parser::ComponentValue>& source, Vector<Parser::ComponentValue>& dest) const
bool StyleComputer::expand_variables(DOM::Element& element, Optional<CSS::Selector::PseudoElement> pseudo_element, StringView property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Parser::TokenStream<Parser::ComponentValue>& source, Vector<Parser::ComponentValue>& dest) const
{
// Arbitrary large value chosen to avoid the billion-laughs attack.
// https://www.w3.org/TR/css-variables-1/#long-variables
@ -705,7 +710,7 @@ bool StyleComputer::expand_variables(DOM::Element& element, StringView property_
auto const& source_function = value.function();
Vector<Parser::ComponentValue> function_values;
Parser::TokenStream source_function_contents { source_function.values() };
if (!expand_variables(element, property_name, dependencies, source_function_contents, function_values))
if (!expand_variables(element, pseudo_element, property_name, dependencies, source_function_contents, function_values))
return false;
NonnullRefPtr<Parser::Function> function = Parser::Function::create(FlyString::from_utf8(source_function.name()).release_value_but_fixme_should_propagate_errors(), move(function_values));
dest.empend(function);
@ -735,10 +740,10 @@ bool StyleComputer::expand_variables(DOM::Element& element, StringView property_
if (parent->has_cycles())
return false;
if (auto custom_property_value = get_custom_property(element, FlyString::from_utf8(custom_property_name).release_value_but_fixme_should_propagate_errors())) {
if (auto custom_property_value = get_custom_property(element, pseudo_element, FlyString::from_utf8(custom_property_name).release_value_but_fixme_should_propagate_errors())) {
VERIFY(custom_property_value->is_unresolved());
Parser::TokenStream custom_property_tokens { custom_property_value->as_unresolved().values() };
if (!expand_variables(element, custom_property_name, dependencies, custom_property_tokens, dest))
if (!expand_variables(element, pseudo_element, custom_property_name, dependencies, custom_property_tokens, dest))
return false;
continue;
}
@ -750,7 +755,7 @@ bool StyleComputer::expand_variables(DOM::Element& element, StringView property_
if (!comma_token.is(Parser::Token::Type::Comma))
return false;
var_contents.skip_whitespace();
if (!expand_variables(element, property_name, dependencies, var_contents, dest))
if (!expand_variables(element, pseudo_element, property_name, dependencies, var_contents, dest))
return false;
}
}
@ -850,7 +855,7 @@ bool StyleComputer::expand_unresolved_values(DOM::Element& element, StringView p
return true;
}
RefPtr<StyleValue> StyleComputer::resolve_unresolved_style_value(DOM::Element& element, PropertyID property_id, UnresolvedStyleValue const& unresolved) const
RefPtr<StyleValue> StyleComputer::resolve_unresolved_style_value(DOM::Element& element, Optional<CSS::Selector::PseudoElement> pseudo_element, PropertyID property_id, UnresolvedStyleValue const& unresolved) const
{
// Unresolved always contains a var() or attr(), unless it is a custom property's value, in which case we shouldn't be trying
// to produce a different StyleValue from it.
@ -860,7 +865,7 @@ RefPtr<StyleValue> StyleComputer::resolve_unresolved_style_value(DOM::Element& e
Vector<Parser::ComponentValue> values_with_variables_expanded;
HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>> dependencies;
if (!expand_variables(element, string_from_property_id(property_id), dependencies, unresolved_values_without_variables_expanded, values_with_variables_expanded))
if (!expand_variables(element, pseudo_element, string_from_property_id(property_id), dependencies, unresolved_values_without_variables_expanded, values_with_variables_expanded))
return {};
Parser::TokenStream unresolved_values_with_variables_expanded { values_with_variables_expanded };
@ -882,7 +887,7 @@ void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& e
continue;
auto property_value = property.value;
if (property.value->is_unresolved()) {
if (auto resolved = resolve_unresolved_style_value(element, property.property_id, property.value->as_unresolved()))
if (auto resolved = resolve_unresolved_style_value(element, pseudo_element, property.property_id, property.value->as_unresolved()))
property_value = resolved.release_nonnull();
}
if (!property_value->is_unresolved())
@ -897,7 +902,7 @@ void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& e
continue;
auto property_value = property.value;
if (property.value->is_unresolved()) {
if (auto resolved = resolve_unresolved_style_value(element, property.property_id, property.value->as_unresolved()))
if (auto resolved = resolve_unresolved_style_value(element, pseudo_element, property.property_id, property.value->as_unresolved()))
property_value = resolved.release_nonnull();
}
if (!property_value->is_unresolved())
@ -907,13 +912,16 @@ void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& e
}
}
static ErrorOr<void> cascade_custom_properties(DOM::Element& element, Vector<MatchingRule> const& matching_rules)
static ErrorOr<void> cascade_custom_properties(DOM::Element& element, Optional<CSS::Selector::PseudoElement> pseudo_element, Vector<MatchingRule> const& matching_rules)
{
size_t needed_capacity = 0;
for (auto const& matching_rule : matching_rules)
needed_capacity += verify_cast<PropertyOwningCSSStyleDeclaration>(matching_rule.rule->declaration()).custom_properties().size();
if (auto const* inline_style = verify_cast<PropertyOwningCSSStyleDeclaration>(element.inline_style()))
needed_capacity += inline_style->custom_properties().size();
if (!pseudo_element.has_value()) {
if (auto const* inline_style = verify_cast<PropertyOwningCSSStyleDeclaration>(element.inline_style()))
needed_capacity += inline_style->custom_properties().size();
}
HashMap<DeprecatedFlyString, StyleProperty> custom_properties;
TRY(custom_properties.try_ensure_capacity(needed_capacity));
@ -923,12 +931,14 @@ static ErrorOr<void> cascade_custom_properties(DOM::Element& element, Vector<Mat
custom_properties.set(it.key, it.value);
}
if (auto const* inline_style = verify_cast<PropertyOwningCSSStyleDeclaration>(element.inline_style())) {
for (auto const& it : inline_style->custom_properties())
custom_properties.set(it.key, it.value);
if (!pseudo_element.has_value()) {
if (auto const* inline_style = verify_cast<PropertyOwningCSSStyleDeclaration>(element.inline_style())) {
for (auto const& it : inline_style->custom_properties())
custom_properties.set(it.key, it.value);
}
}
element.set_custom_properties(move(custom_properties));
element.set_custom_properties(pseudo_element, move(custom_properties));
return {};
}
@ -953,9 +963,7 @@ ErrorOr<void> StyleComputer::compute_cascaded_values(StyleProperties& style, DOM
}
// Then we resolve all the CSS custom properties ("variables") for this element:
// FIXME: Look into how custom properties should interact with pseudo elements and support that properly.
if (!pseudo_element.has_value())
TRY(cascade_custom_properties(element, matching_rule_set.author_rules));
TRY(cascade_custom_properties(element, pseudo_element, matching_rule_set.author_rules));
// Then we apply the declarations from the matched rules in cascade order:

View file

@ -94,8 +94,8 @@ private:
void compute_defaulted_property_value(StyleProperties&, DOM::Element const*, CSS::PropertyID, Optional<CSS::Selector::PseudoElement>) const;
RefPtr<StyleValue> resolve_unresolved_style_value(DOM::Element&, PropertyID, UnresolvedStyleValue const&) const;
bool expand_variables(DOM::Element&, StringView property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Parser::TokenStream<Parser::ComponentValue>& source, Vector<Parser::ComponentValue>& dest) const;
RefPtr<StyleValue> resolve_unresolved_style_value(DOM::Element&, Optional<CSS::Selector::PseudoElement>, PropertyID, UnresolvedStyleValue const&) const;
bool expand_variables(DOM::Element&, Optional<CSS::Selector::PseudoElement>, StringView property_name, HashMap<FlyString, NonnullRefPtr<PropertyDependencyNode>>& dependencies, Parser::TokenStream<Parser::ComponentValue>& source, Vector<Parser::ComponentValue>& dest) const;
bool expand_unresolved_values(DOM::Element&, StringView property_name, Parser::TokenStream<Parser::ComponentValue>& source, Vector<Parser::ComponentValue>& dest) const;
template<typename Callback>

View file

@ -1708,4 +1708,20 @@ void Element::set_computed_css_values(RefPtr<CSS::StyleProperties> style)
m_computed_css_values = move(style);
}
void Element::set_custom_properties(Optional<CSS::Selector::PseudoElement> pseudo_element, HashMap<DeprecatedFlyString, CSS::StyleProperty> custom_properties)
{
if (!pseudo_element.has_value()) {
m_custom_properties = move(custom_properties);
return;
}
m_pseudo_element_custom_properties[to_underlying(pseudo_element.value())] = move(custom_properties);
}
HashMap<DeprecatedFlyString, CSS::StyleProperty> const& Element::custom_properties(Optional<CSS::Selector::PseudoElement> pseudo_element) const
{
if (!pseudo_element.has_value())
return m_custom_properties;
return m_pseudo_element_custom_properties[to_underlying(pseudo_element.value())];
}
}

View file

@ -158,8 +158,8 @@ public:
ShadowRoot const* shadow_root_internal() const { return m_shadow_root.ptr(); }
void set_shadow_root(JS::GCPtr<ShadowRoot>);
void set_custom_properties(HashMap<DeprecatedFlyString, CSS::StyleProperty> custom_properties) { m_custom_properties = move(custom_properties); }
HashMap<DeprecatedFlyString, CSS::StyleProperty> const& custom_properties() const { return m_custom_properties; }
void set_custom_properties(Optional<CSS::Selector::PseudoElement>, HashMap<DeprecatedFlyString, CSS::StyleProperty> custom_properties);
[[nodiscard]] HashMap<DeprecatedFlyString, CSS::StyleProperty> const& custom_properties(Optional<CSS::Selector::PseudoElement>) const;
void queue_an_element_task(HTML::Task::Source, JS::SafeFunction<void()>);
@ -318,6 +318,7 @@ private:
RefPtr<CSS::StyleProperties> m_computed_css_values;
HashMap<DeprecatedFlyString, CSS::StyleProperty> m_custom_properties;
Array<HashMap<DeprecatedFlyString, CSS::StyleProperty>, to_underlying(CSS::Selector::PseudoElement::PseudoElementCount)> m_pseudo_element_custom_properties;
Vector<FlyString> m_classes;

View file

@ -455,14 +455,14 @@ Messages::WebContentServer::InspectDomNodeResponse ConnectionFromClient::inspect
return builder.to_deprecated_string();
};
auto serialize_custom_properties_json = [](Web::DOM::Element const& element) -> DeprecatedString {
auto serialize_custom_properties_json = [](Web::DOM::Element const& element, Optional<Web::CSS::Selector::PseudoElement> pseudo_element) -> DeprecatedString {
StringBuilder builder;
auto serializer = MUST(JsonObjectSerializer<>::try_create(builder));
HashTable<DeprecatedString> seen_properties;
auto const* element_to_check = &element;
while (element_to_check) {
for (auto const& property : element_to_check->custom_properties()) {
for (auto const& property : element_to_check->custom_properties(pseudo_element)) {
if (!seen_properties.contains(property.key)) {
seen_properties.set(property.key);
MUST(serializer.add(property.key, property.value.value->to_string().release_value_but_fixme_should_propagate_errors().to_deprecated_string()));
@ -519,14 +519,14 @@ Messages::WebContentServer::InspectDomNodeResponse ConnectionFromClient::inspect
auto pseudo_element_style = MUST(page().focused_context().active_document()->style_computer().compute_style(element, pseudo_element));
DeprecatedString computed_values = serialize_json(pseudo_element_style);
DeprecatedString resolved_values = "{}";
DeprecatedString custom_properties_json = "{}";
DeprecatedString custom_properties_json = serialize_custom_properties_json(element, pseudo_element);
DeprecatedString node_box_sizing_json = serialize_node_box_sizing_json(pseudo_element_node.ptr());
return { true, computed_values, resolved_values, custom_properties_json, node_box_sizing_json };
}
DeprecatedString computed_values = serialize_json(*element.computed_css_values());
DeprecatedString resolved_values_json = serialize_json(element.resolved_css_values());
DeprecatedString custom_properties_json = serialize_custom_properties_json(element);
DeprecatedString custom_properties_json = serialize_custom_properties_json(element, {});
DeprecatedString node_box_sizing_json = serialize_node_box_sizing_json(element.layout_node());
return { true, computed_values, resolved_values_json, custom_properties_json, node_box_sizing_json };
}