diff --git a/Tests/LibWeb/Layout/expected/pseudo-element-with-custom-properties-2.txt b/Tests/LibWeb/Layout/expected/pseudo-element-with-custom-properties-2.txt new file mode 100644 index 0000000000..6e145c759e --- /dev/null +++ b/Tests/LibWeb/Layout/expected/pseudo-element-with-custom-properties-2.txt @@ -0,0 +1,60 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x470.195312 [BFC] children: not-inline + BlockContainer 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
+ TextNode <#text> + BlockContainer 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
+ BreakNode
+ TextNode <#text> + BreakNode
+ TextNode <#text> + BlockContainer 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
+ BreakNode
+ TextNode <#text> + BreakNode
+ TextNode <#text> + BlockContainer 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> diff --git a/Tests/LibWeb/Layout/expected/pseudo-element-with-custom-properties.txt b/Tests/LibWeb/Layout/expected/pseudo-element-with-custom-properties.txt new file mode 100644 index 0000000000..64f4b3fc9d --- /dev/null +++ b/Tests/LibWeb/Layout/expected/pseudo-element-with-custom-properties.txt @@ -0,0 +1,9 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x16 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x0 children: not-inline + BlockContainer 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> diff --git a/Tests/LibWeb/Layout/input/pseudo-element-with-custom-properties-2.html b/Tests/LibWeb/Layout/input/pseudo-element-with-custom-properties-2.html new file mode 100644 index 0000000000..d896a851d8 --- /dev/null +++ b/Tests/LibWeb/Layout/input/pseudo-element-with-custom-properties-2.html @@ -0,0 +1,32 @@ + +Variable set by inline style of element:
+
+

+Variable set by CSS rule matching element:
+
+

+Variable set by CSS rule matching pseudo element:
+
diff --git a/Tests/LibWeb/Layout/input/pseudo-element-with-custom-properties.html b/Tests/LibWeb/Layout/input/pseudo-element-with-custom-properties.html new file mode 100644 index 0000000000..7114a04e1b --- /dev/null +++ b/Tests/LibWeb/Layout/input/pseudo-element-with-custom-properties.html @@ -0,0 +1,15 @@ +
\ No newline at end of file diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index 5929b78893..47fdbc5be1 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -668,16 +668,21 @@ static void set_property_expanding_shorthands(StyleProperties& style, CSS::Prope style.set_property(property_id, value); } -static RefPtr get_custom_property(DOM::Element const& element, FlyString const& custom_property_name) +static RefPtr get_custom_property(DOM::Element const& element, Optional 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>& dependencies, Parser::TokenStream& source, Vector& dest) const +bool StyleComputer::expand_variables(DOM::Element& element, Optional pseudo_element, StringView property_name, HashMap>& dependencies, Parser::TokenStream& source, Vector& 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 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 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 StyleComputer::resolve_unresolved_style_value(DOM::Element& element, PropertyID property_id, UnresolvedStyleValue const& unresolved) const +RefPtr StyleComputer::resolve_unresolved_style_value(DOM::Element& element, Optional 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 StyleComputer::resolve_unresolved_style_value(DOM::Element& e Vector values_with_variables_expanded; HashMap> 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 cascade_custom_properties(DOM::Element& element, Vector const& matching_rules) +static ErrorOr cascade_custom_properties(DOM::Element& element, Optional pseudo_element, Vector const& matching_rules) { size_t needed_capacity = 0; for (auto const& matching_rule : matching_rules) needed_capacity += verify_cast(matching_rule.rule->declaration()).custom_properties().size(); - if (auto const* inline_style = verify_cast(element.inline_style())) - needed_capacity += inline_style->custom_properties().size(); + + if (!pseudo_element.has_value()) { + if (auto const* inline_style = verify_cast(element.inline_style())) + needed_capacity += inline_style->custom_properties().size(); + } HashMap custom_properties; TRY(custom_properties.try_ensure_capacity(needed_capacity)); @@ -923,12 +931,14 @@ static ErrorOr cascade_custom_properties(DOM::Element& element, Vector(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(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 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: diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.h b/Userland/Libraries/LibWeb/CSS/StyleComputer.h index afdd7d96f4..2503075a9e 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.h @@ -94,8 +94,8 @@ private: void compute_defaulted_property_value(StyleProperties&, DOM::Element const*, CSS::PropertyID, Optional) const; - RefPtr resolve_unresolved_style_value(DOM::Element&, PropertyID, UnresolvedStyleValue const&) const; - bool expand_variables(DOM::Element&, StringView property_name, HashMap>& dependencies, Parser::TokenStream& source, Vector& dest) const; + RefPtr resolve_unresolved_style_value(DOM::Element&, Optional, PropertyID, UnresolvedStyleValue const&) const; + bool expand_variables(DOM::Element&, Optional, StringView property_name, HashMap>& dependencies, Parser::TokenStream& source, Vector& dest) const; bool expand_unresolved_values(DOM::Element&, StringView property_name, Parser::TokenStream& source, Vector& dest) const; template diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index 5a032c3ce0..1f0d441ee8 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -1708,4 +1708,20 @@ void Element::set_computed_css_values(RefPtr style) m_computed_css_values = move(style); } +void Element::set_custom_properties(Optional pseudo_element, HashMap 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 const& Element::custom_properties(Optional pseudo_element) const +{ + if (!pseudo_element.has_value()) + return m_custom_properties; + return m_pseudo_element_custom_properties[to_underlying(pseudo_element.value())]; +} + } diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index b34efa081e..533dd3b716 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -158,8 +158,8 @@ public: ShadowRoot const* shadow_root_internal() const { return m_shadow_root.ptr(); } void set_shadow_root(JS::GCPtr); - void set_custom_properties(HashMap custom_properties) { m_custom_properties = move(custom_properties); } - HashMap const& custom_properties() const { return m_custom_properties; } + void set_custom_properties(Optional, HashMap custom_properties); + [[nodiscard]] HashMap const& custom_properties(Optional) const; void queue_an_element_task(HTML::Task::Source, JS::SafeFunction); @@ -318,6 +318,7 @@ private: RefPtr m_computed_css_values; HashMap m_custom_properties; + Array, to_underlying(CSS::Selector::PseudoElement::PseudoElementCount)> m_pseudo_element_custom_properties; Vector m_classes; diff --git a/Userland/Services/WebContent/ConnectionFromClient.cpp b/Userland/Services/WebContent/ConnectionFromClient.cpp index c248d6a0d0..e56628039c 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.cpp +++ b/Userland/Services/WebContent/ConnectionFromClient.cpp @@ -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 pseudo_element) -> DeprecatedString { StringBuilder builder; auto serializer = MUST(JsonObjectSerializer<>::try_create(builder)); HashTable 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 }; }